mnfst 0.5.112 → 0.5.113
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/manifest.export.js +73 -40
- package/lib/manifest.integrity.json +1 -1
- package/package.json +1 -1
package/lib/manifest.export.js
CHANGED
|
@@ -232,8 +232,17 @@ function initializeExportPlugin() {
|
|
|
232
232
|
return !!(node && node.nodeType === 1
|
|
233
233
|
&& node.hasAttribute && node.hasAttribute('data-no-export'));
|
|
234
234
|
};
|
|
235
|
+
// Default scale = device pixel ratio. On a retina display that's 2,
|
|
236
|
+
// so the export matches what the user actually sees on screen. On a
|
|
237
|
+
// standard 1x display it's 1, so the export is the literal CSS
|
|
238
|
+
// pixel size of the region — no surprise doubling. Callers can
|
|
239
|
+
// explicitly set `resolution: N` to render at N× independent of the
|
|
240
|
+
// host display (useful for "always-2x" archive renders or "1x" thumbs).
|
|
241
|
+
const defaultScale = (typeof window !== 'undefined' && window.devicePixelRatio > 0)
|
|
242
|
+
? window.devicePixelRatio
|
|
243
|
+
: 1;
|
|
235
244
|
const out = {
|
|
236
|
-
scale: Number(opts.resolution) > 0 ? Number(opts.resolution) :
|
|
245
|
+
scale: Number(opts.resolution) > 0 ? Number(opts.resolution) : defaultScale,
|
|
237
246
|
ignoreElements,
|
|
238
247
|
useCORS: true,
|
|
239
248
|
allowTaint: false,
|
|
@@ -342,53 +351,87 @@ function initializeExportPlugin() {
|
|
|
342
351
|
// - The page's own @media print rules
|
|
343
352
|
// Whole-page is the default; passing a target adds a temporary
|
|
344
353
|
// @media print stylesheet that hides everything outside that subtree.
|
|
345
|
-
return printToPdf(filename, opts.target ? resolveTarget(opts) : null);
|
|
354
|
+
return printToPdf(filename, opts.target ? resolveTarget(opts) : null, opts.pageSize);
|
|
346
355
|
}
|
|
347
356
|
|
|
348
357
|
// Trigger the browser's "Save as PDF" dialog. When `target` is provided,
|
|
349
|
-
// the print is scoped to that element
|
|
350
|
-
//
|
|
351
|
-
//
|
|
352
|
-
|
|
358
|
+
// the print is scoped to just that element; otherwise the whole page
|
|
359
|
+
// prints. Filename uses the document.title swap trick — the print dialog
|
|
360
|
+
// seeds its suggested name from the title.
|
|
361
|
+
//
|
|
362
|
+
// Target-scoping strategy: clone the target into a fresh body-level
|
|
363
|
+
// container and `display: none` every other body child. Cloning (rather
|
|
364
|
+
// than relocating) leaves the live DOM intact — Alpine state, event
|
|
365
|
+
// listeners, and any in-flight reactivity stay where they were. The
|
|
366
|
+
// earlier visibility:hidden approach preserved layout space, so the body
|
|
367
|
+
// still occupied its full natural height during print → blank trailing
|
|
368
|
+
// pages. display:none actually removes that space, so the printed pages
|
|
369
|
+
// fit the target's content with no whitespace tail.
|
|
370
|
+
function printToPdf(filename, target, pageSize) {
|
|
353
371
|
const original = document.title;
|
|
354
372
|
const cleaned = String(filename || '').replace(/\.pdf$/i, '') || original;
|
|
355
373
|
document.title = cleaned;
|
|
374
|
+
// Sanitize pageSize: only allow letters/digits/spaces so an
|
|
375
|
+
// attacker-controlled value can't break out of the @page rule.
|
|
376
|
+
const safePageSize = (typeof pageSize === 'string' && /^[a-zA-Z0-9 ]+$/.test(pageSize))
|
|
377
|
+
? pageSize.trim()
|
|
378
|
+
: 'a4';
|
|
379
|
+
|
|
380
|
+
let printContainer = null;
|
|
381
|
+
// Always inject a @page rule so the chosen pageSize applies for both
|
|
382
|
+
// whole-page and targeted prints.
|
|
383
|
+
const pageRule = '@page { size: ' + safePageSize + '; margin: 1cm; }';
|
|
384
|
+
const style = document.createElement('style');
|
|
385
|
+
style.setAttribute('data-mnfst-print-scope', '');
|
|
356
386
|
|
|
357
|
-
let style = null;
|
|
358
|
-
let injectedId = null;
|
|
359
387
|
if (target && target.nodeType === 1) {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
//
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
// positioning calculations remain sane), then we un-hide the
|
|
372
|
-
// target subtree and pin it to the top-left of the print page.
|
|
388
|
+
printContainer = document.createElement('div');
|
|
389
|
+
printContainer.setAttribute('data-mnfst-print-target', '');
|
|
390
|
+
// Anchor off-screen so the clone never flashes visibly in normal
|
|
391
|
+
// (non-print) rendering. The print stylesheet below resets these
|
|
392
|
+
// for the print medium only.
|
|
393
|
+
printContainer.style.position = 'absolute';
|
|
394
|
+
printContainer.style.left = '-99999px';
|
|
395
|
+
printContainer.style.top = '0';
|
|
396
|
+
printContainer.appendChild(target.cloneNode(true));
|
|
397
|
+
document.body.appendChild(printContainer);
|
|
398
|
+
|
|
373
399
|
style.textContent =
|
|
400
|
+
pageRule +
|
|
374
401
|
'@media print {' +
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
402
|
+
// Hide every original body child — display:none takes them
|
|
403
|
+
// out of layout entirely, so body's height collapses to
|
|
404
|
+
// just the clone's height.
|
|
405
|
+
'body > *:not([data-mnfst-print-target]) { display: none !important; }' +
|
|
406
|
+
// Restore the clone to normal flow at the top-left of
|
|
407
|
+
// the page, full width.
|
|
408
|
+
'body > [data-mnfst-print-target] {' +
|
|
409
|
+
'display: block !important;' +
|
|
410
|
+
'position: static !important;' +
|
|
411
|
+
'left: auto !important;' +
|
|
412
|
+
'top: auto !important;' +
|
|
413
|
+
'width: 100% !important;' +
|
|
414
|
+
'max-width: none !important;' +
|
|
381
415
|
'margin: 0 !important;' +
|
|
416
|
+
'padding: 0 !important;' +
|
|
382
417
|
'}' +
|
|
383
418
|
'[data-no-export] { display: none !important; }' +
|
|
384
419
|
'}';
|
|
385
|
-
|
|
420
|
+
} else {
|
|
421
|
+
// Whole-page print: just honor pageSize and the data-no-export
|
|
422
|
+
// filter; let the page's own print CSS do everything else.
|
|
423
|
+
style.textContent =
|
|
424
|
+
pageRule +
|
|
425
|
+
'@media print {' +
|
|
426
|
+
'[data-no-export] { display: none !important; }' +
|
|
427
|
+
'}';
|
|
386
428
|
}
|
|
429
|
+
document.head.appendChild(style);
|
|
387
430
|
|
|
388
431
|
const cleanup = () => {
|
|
389
432
|
document.title = original;
|
|
390
|
-
|
|
391
|
-
if (
|
|
433
|
+
style.remove();
|
|
434
|
+
if (printContainer) printContainer.remove();
|
|
392
435
|
window.removeEventListener('afterprint', onAfter);
|
|
393
436
|
};
|
|
394
437
|
const onAfter = () => cleanup();
|
|
@@ -406,16 +449,6 @@ function initializeExportPlugin() {
|
|
|
406
449
|
setTimeout(cleanup, 30000);
|
|
407
450
|
}
|
|
408
451
|
|
|
409
|
-
// CSS.escape polyfill — used to safely build #id selectors from arbitrary
|
|
410
|
-
// ids. Real `CSS.escape` is widely available; fall back for very old
|
|
411
|
-
// browsers to a conservative regex.
|
|
412
|
-
function cssEscape(value) {
|
|
413
|
-
if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {
|
|
414
|
-
return CSS.escape(String(value));
|
|
415
|
-
}
|
|
416
|
-
return String(value).replace(/[^a-zA-Z0-9_-]/g, (c) => '\\' + c);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
452
|
function effectivePageBackground() {
|
|
420
453
|
let el = document.body;
|
|
421
454
|
while (el && el !== document.documentElement.parentElement) {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"manifest.components.js": "sha384-3dCTD5EwCZTiX+1obYtDNM3WWwPh2JDQUQQsdRUUK3gs6FXjse1ShkKaT/2jsNaI",
|
|
9
9
|
"manifest.data.js": "sha384-pgX6RJRWP7jmWO4ALb+GbS7Gm5JGOrCtxjEOnEcj1aJ8HoGbFjOniyjsntf8IA+B",
|
|
10
10
|
"manifest.dropdowns.js": "sha384-WMrFoSpKfJuo81dyrwhVrDO8rq+rDwh2x8x4nH01BY5ZHkvjE+/SaT2gWCI0zOn+",
|
|
11
|
-
"manifest.export.js": "sha384-
|
|
11
|
+
"manifest.export.js": "sha384-IF2MJHlDpx9hprNtdplNIJJPnU5xE+9/S+LQL3MBG4ERICBtIwOKM5V7RRY/OWzM",
|
|
12
12
|
"manifest.icons.js": "sha384-uOkboYrovjCpl22eey3Jaxpey+pOnot5NDnRRumcRxiR7IOVaRh1i20gYnWXR5dW",
|
|
13
13
|
"manifest.localization.js": "sha384-M40EWrbWs2MoJvUiYVQaPxPiOzMYBn/ywuMR02rvoSXG77eIHT/aRoYub9r6+jC+",
|
|
14
14
|
"manifest.markdown.js": "sha384-jJFVCg46bYcP/xglX6QguG2qsGi2I/0o9kXLpM2RMq7edbTYpgx6WQ9iNB7kKHmN",
|