docnest 0.1.14 → 0.1.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docnest",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "可复用的本地 Markdown 文档中心",
5
5
  "repository": {
6
6
  "type": "git",
@@ -179,13 +179,26 @@ body {
179
179
  }
180
180
 
181
181
  .doc-pdf-export-root {
182
+ /* PDF pages use a light paper palette, independently of the live reading mode. */
183
+ --linear-bg: #ffffff;
184
+ --linear-bg-subtle: #fafafa;
185
+ --linear-border: #e5e5e5;
186
+ --linear-text: #111111;
187
+ --linear-text-muted: #6b6b6b;
188
+ --linear-accent: #5e6ad2;
189
+ --linear-accent-hover: #4c57b8;
190
+ --doc-surface-raised: #ffffff;
191
+ --doc-rule-strong: #bfc0c5;
192
+ --doc-accent-soft: rgba(94, 106, 210, 0.12);
193
+ --doc-code-bg: #f6f7f8;
194
+ --doc-code-text: #24262b;
182
195
  position: absolute;
183
196
  left: 0;
184
197
  top: 0;
185
198
  width: 960px;
186
199
  padding: 24px 32px;
187
- background: #ffffff;
188
- color: #111111;
200
+ background: var(--doc-surface-raised);
201
+ color: var(--linear-text);
189
202
  z-index: -1;
190
203
  pointer-events: none;
191
204
  overflow: visible;
@@ -379,7 +379,8 @@ body[data-doc-theme][data-theme="dark"] .pdf-export-modal__btn {
379
379
  }
380
380
 
381
381
  /* Theme 01: Slate Modern — the package's original visual language. */
382
- body[data-doc-theme="slate-modern"] {
382
+ body[data-doc-theme="slate-modern"],
383
+ .doc-pdf-export-root[data-doc-theme="slate-modern"] {
383
384
  --doc-font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
384
385
  "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
385
386
  --doc-font-reading: var(--doc-font-ui);
@@ -387,7 +388,8 @@ body[data-doc-theme="slate-modern"] {
387
388
  }
388
389
 
389
390
  /* Theme 02: Editorial Atlas */
390
- body[data-doc-theme="editorial-atlas"] {
391
+ body[data-doc-theme="editorial-atlas"],
392
+ .doc-pdf-export-root[data-doc-theme="editorial-atlas"] {
391
393
  --linear-bg: #f3f0e7;
392
394
  --linear-bg-subtle: #ebe6da;
393
395
  --linear-sidebar: #eee9dd;
@@ -490,7 +492,8 @@ body[data-doc-theme="editorial-atlas"] .doc-toc {
490
492
  }
491
493
 
492
494
  /* Theme 03: Precision Index */
493
- body[data-doc-theme="precision-index"] {
495
+ body[data-doc-theme="precision-index"],
496
+ .doc-pdf-export-root[data-doc-theme="precision-index"] {
494
497
  --linear-bg: #eef2f3;
495
498
  --linear-bg-subtle: #e4eaec;
496
499
  --linear-sidebar: #e9eef0;
@@ -640,7 +643,8 @@ body[data-doc-theme="precision-index"] .doc-toc-title {
640
643
  }
641
644
 
642
645
  /* Theme 04: Archive Room */
643
- body[data-doc-theme="archive-room"] {
646
+ body[data-doc-theme="archive-room"],
647
+ .doc-pdf-export-root[data-doc-theme="archive-room"] {
644
648
  --linear-bg: #f3efe4;
645
649
  --linear-bg-subtle: #e9e2d3;
646
650
  --linear-sidebar: #ebe5d8;
@@ -775,7 +779,8 @@ body[data-doc-theme="archive-room"] .doc-toc {
775
779
  }
776
780
 
777
781
  /* Theme 05: Swiss Manual */
778
- body[data-doc-theme="swiss-manual"] {
782
+ body[data-doc-theme="swiss-manual"],
783
+ .doc-pdf-export-root[data-doc-theme="swiss-manual"] {
779
784
  --linear-bg: #f4f3ef;
780
785
  --linear-bg-subtle: #e9e7e0;
781
786
  --linear-sidebar: #eeece6;
@@ -93,9 +93,40 @@
93
93
  function createExportRoot(article) {
94
94
  var root = document.createElement('div');
95
95
  root.className = EXPORT_CLASS;
96
+ root.setAttribute('data-doc-theme', document.body.getAttribute('data-doc-theme') || 'slate-modern');
97
+ root.setAttribute('data-theme', 'light');
96
98
 
97
99
  var clone = article.cloneNode(true);
98
100
  removeTransientNodes(clone);
101
+ // html2canvas may paint closed <details> descendants without reserving their
102
+ // height. Export static, expanded sections while leaving the live page alone.
103
+ Array.prototype.slice.call(clone.querySelectorAll('details')).reverse().forEach(function(details) {
104
+ var section = document.createElement('section');
105
+ Array.prototype.slice.call(details.attributes).forEach(function(attribute) {
106
+ if (attribute.name !== 'open') section.setAttribute(attribute.name, attribute.value);
107
+ });
108
+ section.style.display = 'block';
109
+ section.style.height = 'auto';
110
+ section.style.maxHeight = 'none';
111
+ section.style.overflow = 'visible';
112
+ section.style.margin = '1em 0';
113
+ while (details.firstChild) {
114
+ var child = details.firstChild;
115
+ if (child.tagName === 'SUMMARY') {
116
+ var heading = document.createElement('div');
117
+ heading.className = 'doc-pdf-section-title';
118
+ heading.style.fontWeight = '700';
119
+ heading.style.marginBottom = '12px';
120
+ while (child.firstChild) heading.appendChild(child.firstChild);
121
+ section.appendChild(heading);
122
+ details.removeChild(child);
123
+ } else {
124
+ section.appendChild(child);
125
+ }
126
+ }
127
+ details.parentNode.replaceChild(section, details);
128
+ });
129
+ clone.querySelectorAll('img').forEach(function(img) { img.loading = 'eager'; });
99
130
  root.appendChild(clone);
100
131
  document.body.appendChild(root);
101
132
 
@@ -137,18 +168,60 @@
137
168
  return breakpoints;
138
169
  }
139
170
 
171
+ function collectProtectedRegions(root, canvasScale) {
172
+ var rootRect = root.getBoundingClientRect();
173
+ var regions = [];
174
+ root.querySelectorAll('img, svg, canvas, figure, .mermaid').forEach(function(node) {
175
+ var rect = node.getBoundingClientRect();
176
+ if (rect.width <= 0 || rect.height <= 0) return;
177
+ regions.push({
178
+ top: Math.max(0, Math.floor((rect.top - rootRect.top) * canvasScale)),
179
+ bottom: Math.max(0, Math.ceil((rect.bottom - rootRect.top) * canvasScale)),
180
+ });
181
+ });
182
+ regions.sort(function(a, b) { return a.top - b.top || b.bottom - a.bottom; });
183
+ var merged = [];
184
+ regions.forEach(function(region) {
185
+ var last = merged[merged.length - 1];
186
+ if (last && region.top < last.bottom) {
187
+ last.bottom = Math.max(last.bottom, region.bottom);
188
+ } else {
189
+ merged.push(region);
190
+ }
191
+ });
192
+ return merged;
193
+ }
194
+
140
195
  function cleanupExportRoot(root) {
141
196
  if (root && root.parentNode) {
142
197
  root.parentNode.removeChild(root);
143
198
  }
144
199
  }
145
200
 
146
- function waitForExportReady() {
201
+ function waitForExportReady(root) {
147
202
  var fontReady = document.fonts && document.fonts.ready
148
203
  ? document.fonts.ready.catch(function() {})
149
204
  : Promise.resolve();
150
205
 
151
- return fontReady.then(function() {
206
+ var imagesReady = Promise.all(Array.prototype.map.call(root.querySelectorAll('img'), function(img) {
207
+ if (img.complete) {
208
+ return img.naturalWidth > 0 ? Promise.resolve() : Promise.reject(new Error('文档图片加载失败'));
209
+ }
210
+ return new Promise(function(resolve, reject) {
211
+ var timer = setTimeout(function() { finish(new Error('等待文档图片超时')); }, 15000);
212
+ function finish(error) {
213
+ clearTimeout(timer);
214
+ img.removeEventListener('load', loaded);
215
+ img.removeEventListener('error', failed);
216
+ if (error) reject(error); else resolve();
217
+ }
218
+ function loaded() { finish(); }
219
+ function failed() { finish(new Error('文档图片加载失败')); }
220
+ img.addEventListener('load', loaded, { once: true });
221
+ img.addEventListener('error', failed, { once: true });
222
+ });
223
+ }));
224
+ return Promise.all([fontReady, imagesReady]).then(function() {
152
225
  return new Promise(function(resolve) {
153
226
  requestAnimationFrame(function() {
154
227
  requestAnimationFrame(resolve);
@@ -169,8 +242,9 @@
169
242
  logging: false,
170
243
  width: width,
171
244
  height: height,
172
- windowWidth: width,
173
- windowHeight: height,
245
+ // Keep vw/vh styles and media queries identical to the measured DOM.
246
+ windowWidth: window.innerWidth,
247
+ windowHeight: window.innerHeight,
174
248
  scrollX: 0,
175
249
  scrollY: 0,
176
250
  });
@@ -313,7 +387,7 @@
313
387
  return Math.max(minBreakY - startY, searchStartY + bestRow - startY);
314
388
  }
315
389
 
316
- function addCanvasToPdf(pdf, canvas, margin, blockBreakpoints, watermark) {
390
+ function addCanvasToPdf(pdf, canvas, margin, blockBreakpoints, watermark, protectedRegions) {
317
391
  var pageWidth = pdf.internal.pageSize.getWidth();
318
392
  var pageHeight = pdf.internal.pageSize.getHeight();
319
393
  var usableWidth = pageWidth - margin * 2;
@@ -338,6 +412,16 @@
338
412
  sliceHeight = findBestPageBreak(canvas, sourceCtx, offsetY, pageCanvasHeight);
339
413
  }
340
414
  }
415
+ sliceHeight = Math.min(sliceHeight, remainingHeight, pageCanvasHeight);
416
+ var sliceEnd = offsetY + sliceHeight;
417
+ (protectedRegions || []).forEach(function(region) {
418
+ if (region.top < sliceEnd && region.bottom > sliceEnd) {
419
+ // Move a fitting image to the next page even if this leaves whitespace.
420
+ // A taller-than-page image receives one whole, scaled PDF page.
421
+ sliceEnd = region.top > offsetY ? region.top : Math.min(region.bottom, canvas.height);
422
+ }
423
+ });
424
+ sliceHeight = Math.max(1, sliceEnd - offsetY);
341
425
  var pageCanvas = document.createElement('canvas');
342
426
  pageCanvas.width = canvas.width;
343
427
  pageCanvas.height = sliceHeight;
@@ -362,7 +446,7 @@
362
446
  context,
363
447
  pageCanvas.width,
364
448
  pageCanvas.height,
365
- pageCanvasHeight,
449
+ Math.max(pageCanvasHeight, sliceHeight),
366
450
  watermark.text,
367
451
  );
368
452
  }
@@ -371,13 +455,14 @@
371
455
  pdf.addPage();
372
456
  }
373
457
 
374
- var imageHeight = (sliceHeight * usableWidth) / canvas.width;
458
+ var imageWidth = Math.min(usableWidth, (usableHeight * canvas.width) / sliceHeight);
459
+ var imageHeight = (sliceHeight * imageWidth) / canvas.width;
375
460
  pdf.addImage(
376
461
  pageCanvas.toDataURL('image/png'),
377
462
  'PNG',
463
+ margin + (usableWidth - imageWidth) / 2,
378
464
  margin,
379
- margin,
380
- usableWidth,
465
+ imageWidth,
381
466
  imageHeight,
382
467
  undefined,
383
468
  'FAST'
@@ -586,13 +671,15 @@
586
671
  format: 'a4',
587
672
  compress: true,
588
673
  });
589
- return waitForExportReady()
674
+ return waitForExportReady(exportRoot)
590
675
  .then(function() {
591
676
  return renderCanvas(exportRoot);
592
677
  })
593
678
  .then(function(canvas) {
594
- var blockBreakpoints = collectBlockBreakpoints(exportRoot, canvas.height / Math.max(exportRoot.scrollHeight, 1));
595
- addCanvasToPdf(pdf, canvas, margin, blockBreakpoints, watermark);
679
+ var canvasScale = canvas.height / Math.max(exportRoot.scrollHeight, 1);
680
+ var blockBreakpoints = collectBlockBreakpoints(exportRoot, canvasScale);
681
+ var protectedRegions = collectProtectedRegions(exportRoot, canvasScale);
682
+ addCanvasToPdf(pdf, canvas, margin, blockBreakpoints, watermark, protectedRegions);
596
683
  pdf.save(getExportFileName() + '.pdf');
597
684
  })
598
685
  .finally(function() {