@helping-ai-workflow/md2doc 3.15.0 → 3.16.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.
Files changed (2) hide show
  1. package/lib/md2doc.js +148 -2
  2. package/package.json +1 -1
package/lib/md2doc.js CHANGED
@@ -467,6 +467,7 @@ const READER_ICONS = {
467
467
  x: ICON_SVG_OPEN + '<path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>',
468
468
  copy: ICON_SVG_OPEN + '<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>',
469
469
  check: ICON_SVG_OPEN + '<path d="M20 6 9 17l-5-5"/></svg>',
470
+ camera: ICON_SVG_OPEN + '<path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"/><circle cx="12" cy="13" r="3"/></svg>',
470
471
  };
471
472
 
472
473
  async function renderMarkdown(mdText, srcPath, opts = {}) {
@@ -1492,7 +1493,8 @@ ${itemsHtml}
1492
1493
  <div class="toc-header-actions">
1493
1494
  <button id="toc-expand-all" type="button" aria-label="Expand all" title="Expand all">${READER_ICONS.unfold}</button>
1494
1495
  <button id="toc-collapse-all" type="button" aria-label="Collapse all" title="Collapse all">${READER_ICONS.fold}</button>
1495
- <button id="toc-collapse-toggle" type="button" aria-label="Collapse table of contents" title="Collapse / expand sidebar">${READER_ICONS.panel}</button>
1496
+ ${opts.editMode ? '' : ` <button id="capture-mode-toggle" type="button" aria-label="Screenshot mode" title="Screenshot mode (Esc to exit)">${READER_ICONS.camera}</button>
1497
+ `} <button id="toc-collapse-toggle" type="button" aria-label="Collapse table of contents" title="Collapse / expand sidebar">${READER_ICONS.panel}</button>
1496
1498
  </div>
1497
1499
  <div class="toc-breadcrumb" data-toc-breadcrumb aria-label="Current location"></div>
1498
1500
  </div>
@@ -2411,7 +2413,8 @@ const html = `<!DOCTYPE html>
2411
2413
  TOC-list buttons (no list to act on) and shrink padding so the toggle
2412
2414
  fits inside the rail instead of being clipped by overflow:hidden. */
2413
2415
  body[data-toc-collapsed] #toc-expand-all,
2414
- body[data-toc-collapsed] #toc-collapse-all {
2416
+ body[data-toc-collapsed] #toc-collapse-all,
2417
+ body[data-toc-collapsed] #capture-mode-toggle {
2415
2418
  display: none;
2416
2419
  }
2417
2420
  body[data-toc-collapsed] .toc {
@@ -2881,6 +2884,70 @@ const html = `<!DOCTYPE html>
2881
2884
  min-width: 0;
2882
2885
  flex: 1 1 auto;
2883
2886
  }
2887
+ /* Screenshot mode (v3.16.0). A scroll-and-stitch full-page capture
2888
+ (FireShot and the like) glues one slice per screen, so whatever stays put
2889
+ while the page scrolls lands on every slice: hide all of it, and keep the
2890
+ column at the width it was read at (--md2doc-capture-w, measured on
2891
+ entry), centred, so its lines break as they did. */
2892
+ body[data-capture-mode] .reader-sidebar,
2893
+ body[data-capture-mode] .sidebar-splitter,
2894
+ body[data-capture-mode] .sidebar-scrim,
2895
+ body[data-capture-mode] .mobile-bar,
2896
+ body[data-capture-mode] .md2doc-theme-toggle {
2897
+ display: none;
2898
+ }
2899
+ body[data-capture-mode] .page-layout {
2900
+ display: block;
2901
+ }
2902
+ body[data-capture-mode] main.content {
2903
+ box-sizing: border-box;
2904
+ width: var(--md2doc-capture-w, auto);
2905
+ max-width: 100%;
2906
+ margin: 0 auto;
2907
+ }
2908
+ /* v3.16.1: a capture tool hides every sticky element after its first slice,
2909
+ so a sticky first column came out blank from the second screen down. The
2910
+ runtime also widens each box that scrolls sideways (fitWideBoxes); the
2911
+ rules below are its last resort, for a box still wider than the screen. */
2912
+ body[data-capture-mode] .content table tbody td:first-child,
2913
+ body[data-capture-mode] .content table thead th:first-child {
2914
+ position: static;
2915
+ }
2916
+ body[data-capture-mode] .content table.capture-wrap th:not(.cell-narrow),
2917
+ body[data-capture-mode] .content table.capture-wrap td:not(.cell-narrow),
2918
+ body[data-capture-mode] .content table.capture-wrap td:not(.cell-narrow) code {
2919
+ white-space: normal;
2920
+ overflow-wrap: break-word;
2921
+ min-width: 0;
2922
+ }
2923
+ body[data-capture-mode] .content table.capture-wrap th,
2924
+ body[data-capture-mode] .content table.capture-wrap td {
2925
+ padding: 0.4em 0.6em;
2926
+ }
2927
+ body[data-capture-mode] .content pre.capture-wrap,
2928
+ body[data-capture-mode] .content pre.capture-wrap code {
2929
+ white-space: pre-wrap;
2930
+ overflow-wrap: anywhere;
2931
+ }
2932
+ body[data-capture-mode] .content .diagram-scroll.capture-wrap > svg {
2933
+ max-width: 100% !important;
2934
+ height: auto;
2935
+ }
2936
+ .capture-toast {
2937
+ position: fixed;
2938
+ top: 16px;
2939
+ left: 50%;
2940
+ transform: translateX(-50%);
2941
+ z-index: 300;
2942
+ padding: 8px 16px;
2943
+ border-radius: 999px;
2944
+ background: #24292e;
2945
+ color: #ffffff;
2946
+ font-size: 14px;
2947
+ line-height: 1.4;
2948
+ box-shadow: 0 4px 14px rgba(0,0,0,0.18);
2949
+ pointer-events: none;
2950
+ }
2884
2951
  .content p,
2885
2952
  .content li,
2886
2953
  .content blockquote,
@@ -5057,6 +5124,85 @@ ${mermaidInitTag}`)
5057
5124
  setSidebarOpen(false);
5058
5125
  }
5059
5126
  });
5127
+
5128
+ // ── Screenshot mode (v3.16.0): only the document, for a full-page capture ──
5129
+ // The CSS beside '.capture-toast' hides everything that stays put while the
5130
+ // page scrolls. The column keeps the width measured here, so nothing reflows
5131
+ // and the reading position holds. Esc leaves; a reload never comes back in it.
5132
+ const captureBtn = document.getElementById('capture-mode-toggle');
5133
+ let captureToastTimer = 0;
5134
+ // A capture tool only sees the part of a sideways-scrolling box that is in
5135
+ // view, so each one (a wide table, a long code line, a wide diagram) is
5136
+ // widened to its full width, centred on the screen, at most the screen less
5137
+ // a 16px gutter each side; the prose keeps its reading width. One still too
5138
+ // wide wraps (.capture-wrap), and a table still too wide after that gets
5139
+ // smaller type. Each box's own style attribute is kept and put back on exit.
5140
+ let captureFitted = [];
5141
+ function fitWideBoxes() {
5142
+ const vw = document.documentElement.clientWidth;
5143
+ const boxes = document.querySelectorAll('main.content table, main.content pre, main.content .diagram-scroll');
5144
+ for (const el of boxes) {
5145
+ if (el.scrollWidth <= el.clientWidth + 1) continue;
5146
+ captureFitted.push({ el: el, style: el.getAttribute('style') });
5147
+ const w = Math.min(el.scrollWidth, vw - 32);
5148
+ el.style.boxSizing = 'border-box';
5149
+ el.style.width = w + 'px';
5150
+ el.style.maxWidth = 'none';
5151
+ const margin = parseFloat(getComputedStyle(el).marginLeft) || 0;
5152
+ el.style.marginLeft = (margin + (vw - w) / 2 - el.getBoundingClientRect().left) + 'px';
5153
+ if (el.scrollWidth > el.clientWidth + 1) el.classList.add('capture-wrap');
5154
+ // Many columns on a narrow screen can stay too wide even wrapped: shrink
5155
+ // the table's type (the wrap rule's cell padding is in em) until it fits.
5156
+ let size = parseFloat(getComputedStyle(el).fontSize);
5157
+ for (let i = 0; i < 8 && el.tagName === 'TABLE' && el.scrollWidth > el.clientWidth + 1; i++) {
5158
+ size = size * el.clientWidth / el.scrollWidth * 0.98;
5159
+ el.style.fontSize = size + 'px';
5160
+ }
5161
+ }
5162
+ }
5163
+ function unfitWideBoxes() {
5164
+ for (const f of captureFitted) {
5165
+ f.el.classList.remove('capture-wrap');
5166
+ if (f.style === null) f.el.removeAttribute('style');
5167
+ else f.el.setAttribute('style', f.style);
5168
+ }
5169
+ captureFitted = [];
5170
+ }
5171
+ function setCaptureMode(on) {
5172
+ clearTimeout(captureToastTimer);
5173
+ const oldToast = document.querySelector('.capture-toast');
5174
+ if (oldToast) oldToast.remove();
5175
+ if (!on) {
5176
+ unfitWideBoxes();
5177
+ document.body.removeAttribute('data-capture-mode');
5178
+ document.body.style.removeProperty('--md2doc-capture-w');
5179
+ return;
5180
+ }
5181
+ setSidebarOpen(false);
5182
+ const w = document.querySelector('main.content').getBoundingClientRect().width;
5183
+ document.body.style.setProperty('--md2doc-capture-w', w + 'px');
5184
+ document.body.setAttribute('data-capture-mode', '');
5185
+ unfitWideBoxes();
5186
+ fitWideBoxes();
5187
+ const toast = document.createElement('div');
5188
+ toast.className = 'capture-toast';
5189
+ toast.setAttribute('role', 'status');
5190
+ toast.textContent = 'Screenshot mode — press Esc or reload to exit';
5191
+ document.body.appendChild(toast);
5192
+ // Gone before the capture tool scrolls, or it would be on every slice too.
5193
+ captureToastTimer = setTimeout(() => toast.remove(), 2000);
5194
+ }
5195
+ if (captureBtn) {
5196
+ captureBtn.addEventListener('click', () => setCaptureMode(true));
5197
+ }
5198
+ // Registered ahead of the lightbox's own Esc handler, which closes the
5199
+ // diagram: while one is open, its Esc is the lightbox's and the mode stays.
5200
+ document.addEventListener('keydown', (event) => {
5201
+ if (lightboxIsOpen()) return;
5202
+ if (event.key === 'Escape' && document.body.hasAttribute('data-capture-mode')) {
5203
+ setCaptureMode(false);
5204
+ }
5205
+ });
5060
5206
  document.addEventListener('click', (event) => {
5061
5207
  if (!document.body.hasAttribute('data-sidebar-open')) return;
5062
5208
  if (!event.target || !event.target.closest) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helping-ai-workflow/md2doc",
3
- "version": "3.15.0",
3
+ "version": "3.16.1",
4
4
  "description": "Markdown → HTML / PDF renderer with WaveDrom, Mermaid, Graphviz, and draw.io support",
5
5
  "keywords": [
6
6
  "markdown",