mnfst 0.5.111 → 0.5.112
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.code.js +18 -2
- package/lib/manifest.export.js +102 -37
- package/lib/manifest.integrity.json +4 -4
- package/lib/manifest.markdown.js +10 -1
- package/lib/manifest.router.js +14 -0
- package/package.json +1 -1
package/lib/manifest.code.js
CHANGED
|
@@ -910,7 +910,11 @@ function handleVisible(el) {
|
|
|
910
910
|
const candidates = document.querySelectorAll(
|
|
911
911
|
'[x-code]:not([data-code-processed]),' +
|
|
912
912
|
'[x-code-group]:not([data-group-processed]),' +
|
|
913
|
-
'pre:not([x-code]):not([data-code-processed]) > code[class*="language-"]'
|
|
913
|
+
'pre:not([x-code]):not([data-code-processed]) > code[class*="language-"],' +
|
|
914
|
+
// Copy-only inline codespans from markdown (`text`{copy}). Skip those
|
|
915
|
+
// that also carry x-code — they're already covered by the first
|
|
916
|
+
// selector — and skip <code copy> inside a <pre> (block-level path).
|
|
917
|
+
'code[copy]:not([x-code]):not([data-code-processed]):not(pre > code)'
|
|
914
918
|
);
|
|
915
919
|
// Always include the triggering element (it's already known to be visible).
|
|
916
920
|
processOne(el);
|
|
@@ -931,6 +935,14 @@ function processOne(el) {
|
|
|
931
935
|
} else if (el.matches && el.matches('pre > code[class*="language-"]')) {
|
|
932
936
|
adoptMarkdownBlocks(el.parentElement);
|
|
933
937
|
processCodeElement(el.parentElement);
|
|
938
|
+
} else if (el.tagName === 'CODE' && el.hasAttribute('copy')) {
|
|
939
|
+
// Copy-only inline codespan (`text`{copy} in markdown) — no x-code,
|
|
940
|
+
// no highlighting, just wire the click-to-copy handler. Marking it
|
|
941
|
+
// as processed prevents re-discovery on subsequent scans.
|
|
942
|
+
if (el.dataset.codeProcessed !== 'yes') {
|
|
943
|
+
el.dataset.codeProcessed = 'yes';
|
|
944
|
+
setupInlineCopy(el);
|
|
945
|
+
}
|
|
934
946
|
}
|
|
935
947
|
}
|
|
936
948
|
|
|
@@ -946,7 +958,11 @@ function observeAll(root = document) {
|
|
|
946
958
|
const candidates = [
|
|
947
959
|
...root.querySelectorAll('[x-code]:not([data-code-processed])'),
|
|
948
960
|
...root.querySelectorAll('[x-code-group]:not([data-group-processed])'),
|
|
949
|
-
...root.querySelectorAll('pre:not([x-code]):not([data-code-processed]) > code[class*="language-"]')
|
|
961
|
+
...root.querySelectorAll('pre:not([x-code]):not([data-code-processed]) > code[class*="language-"]'),
|
|
962
|
+
// Copy-only inline codespans (no x-code, not inside <pre>) — markdown's
|
|
963
|
+
// `text`{copy} syntax. These need only the copy handler wired, no
|
|
964
|
+
// highlighting work.
|
|
965
|
+
...root.querySelectorAll('code[copy]:not([x-code]):not([data-code-processed]):not(pre > code)')
|
|
950
966
|
];
|
|
951
967
|
for (const el of candidates) {
|
|
952
968
|
io.observe(el);
|
package/lib/manifest.export.js
CHANGED
|
@@ -249,7 +249,16 @@ function initializeExportPlugin() {
|
|
|
249
249
|
scrollX: 0,
|
|
250
250
|
scrollY: 0,
|
|
251
251
|
};
|
|
252
|
-
|
|
252
|
+
// `backgroundColor: 'transparent'` is the explicit opt-out for raster
|
|
253
|
+
// exports — useful for icons and logos that should composite onto an
|
|
254
|
+
// arbitrary surface. We translate it to null because html2canvas-pro
|
|
255
|
+
// uses `null` (not the string 'transparent') to disable its default
|
|
256
|
+
// white fill.
|
|
257
|
+
if (opts.backgroundColor === 'transparent') {
|
|
258
|
+
out.backgroundColor = null;
|
|
259
|
+
} else if (opts.backgroundColor) {
|
|
260
|
+
out.backgroundColor = opts.backgroundColor;
|
|
261
|
+
}
|
|
253
262
|
if (opts.width) out.width = Number(opts.width);
|
|
254
263
|
if (opts.height) out.height = Number(opts.height);
|
|
255
264
|
return out;
|
|
@@ -291,7 +300,14 @@ function initializeExportPlugin() {
|
|
|
291
300
|
const lib = await loadSnapshotLib();
|
|
292
301
|
const target = resolveTarget(opts);
|
|
293
302
|
const so = snapshotOptions(opts);
|
|
294
|
-
|
|
303
|
+
// Default raster exports to the page's effective background so the
|
|
304
|
+
// snapshot looks like the page itself — a Sales Chart exported from
|
|
305
|
+
// a dark-mode page comes out on a dark background. Callers can
|
|
306
|
+
// explicitly opt into transparency with `backgroundColor: 'transparent'`,
|
|
307
|
+
// which snapshotOptions() translates to the null value html2canvas-pro
|
|
308
|
+
// wants. (Previously only JPEG/WebP got a default fill; PNG defaulted
|
|
309
|
+
// to transparent and rendered white text on a white viewer background.)
|
|
310
|
+
if (so.backgroundColor === undefined) {
|
|
295
311
|
so.backgroundColor = effectivePageBackground();
|
|
296
312
|
}
|
|
297
313
|
await waitForImages(target);
|
|
@@ -306,49 +322,98 @@ function initializeExportPlugin() {
|
|
|
306
322
|
triggerDownload(dataUrl, filename);
|
|
307
323
|
}
|
|
308
324
|
|
|
309
|
-
//
|
|
310
|
-
// It handles multi-page layout, page breaks, vector text,
|
|
311
|
-
// own @media print CSS — far more reliable than
|
|
312
|
-
// and embedding it as a single image
|
|
313
|
-
//
|
|
314
|
-
//
|
|
325
|
+
// PDFs (whole-page or element-scoped) route through the browser's native
|
|
326
|
+
// print pipeline. It handles multi-page layout, page breaks, vector text,
|
|
327
|
+
// and the page's own @media print CSS — far more reliable than
|
|
328
|
+
// rasterizing a long page and embedding it as a single image, and gives
|
|
329
|
+
// selectable / copy-pasteable text in the resulting file. The user sees
|
|
330
|
+
// the print dialog and picks "Save as PDF" (or any installed PDF
|
|
331
|
+
// printer). For element-scoped PDFs, a temporary print stylesheet is
|
|
332
|
+
// injected that hides everything outside the target subtree.
|
|
315
333
|
async function exportPdf(opts, filename) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const pdf = new jsPDFCtor({ orientation, unit: 'pt', format: opts.pageSize || 'a4' });
|
|
329
|
-
const pageW = pdf.internal.pageSize.getWidth();
|
|
330
|
-
const pageH = pdf.internal.pageSize.getHeight();
|
|
331
|
-
const ratio = Math.min(pageW / img.width, pageH / img.height);
|
|
332
|
-
const renderW = img.width * ratio;
|
|
333
|
-
const renderH = img.height * ratio;
|
|
334
|
-
const offsetX = (pageW - renderW) / 2;
|
|
335
|
-
const offsetY = (pageH - renderH) / 2;
|
|
336
|
-
pdf.addImage(dataUrl, 'PNG', offsetX, offsetY, renderW, renderH);
|
|
337
|
-
pdf.save(filename);
|
|
334
|
+
// Both whole-page and targeted PDFs route through the browser's print
|
|
335
|
+
// pipeline. That gives us:
|
|
336
|
+
// - Vector text (selectable, copy/pasteable in the PDF)
|
|
337
|
+
// - Real layout (browser's actual rendering of the target's CSS,
|
|
338
|
+
// including cascade layers, custom properties, and computed
|
|
339
|
+
// fonts — html2canvas-pro's computed-style walker mis-renders
|
|
340
|
+
// heading classes inside @layer rules)
|
|
341
|
+
// - Multi-page flow for tall content
|
|
342
|
+
// - The page's own @media print rules
|
|
343
|
+
// Whole-page is the default; passing a target adds a temporary
|
|
344
|
+
// @media print stylesheet that hides everything outside that subtree.
|
|
345
|
+
return printToPdf(filename, opts.target ? resolveTarget(opts) : null);
|
|
338
346
|
}
|
|
339
347
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
348
|
+
// Trigger the browser's "Save as PDF" dialog. When `target` is provided,
|
|
349
|
+
// the print is scoped to that element via injected @media print CSS;
|
|
350
|
+
// otherwise the whole page prints. Filename uses the document.title swap
|
|
351
|
+
// trick — the print dialog seeds its suggested name from the title.
|
|
352
|
+
function printToPdf(filename, target) {
|
|
344
353
|
const original = document.title;
|
|
345
354
|
const cleaned = String(filename || '').replace(/\.pdf$/i, '') || original;
|
|
346
355
|
document.title = cleaned;
|
|
356
|
+
|
|
357
|
+
let style = null;
|
|
358
|
+
let injectedId = null;
|
|
359
|
+
if (target && target.nodeType === 1) {
|
|
360
|
+
// The target needs an id we can reference from the stylesheet.
|
|
361
|
+
// Give it a throwaway one if it doesn't already have one; we'll
|
|
362
|
+
// remove it on cleanup so we don't leak DOM mutations.
|
|
363
|
+
if (!target.id) {
|
|
364
|
+
injectedId = 'mnfst-print-target-' + Math.random().toString(36).slice(2, 9);
|
|
365
|
+
target.id = injectedId;
|
|
366
|
+
}
|
|
367
|
+
const id = target.id;
|
|
368
|
+
style = document.createElement('style');
|
|
369
|
+
style.setAttribute('data-mnfst-print-scope', '');
|
|
370
|
+
// visibility:hidden preserves layout space (so absolute/fixed
|
|
371
|
+
// positioning calculations remain sane), then we un-hide the
|
|
372
|
+
// target subtree and pin it to the top-left of the print page.
|
|
373
|
+
style.textContent =
|
|
374
|
+
'@media print {' +
|
|
375
|
+
'body * { visibility: hidden !important; }' +
|
|
376
|
+
'#' + cssEscape(id) + ', #' + cssEscape(id) + ' * { visibility: visible !important; }' +
|
|
377
|
+
'#' + cssEscape(id) + ' {' +
|
|
378
|
+
'position: absolute !important;' +
|
|
379
|
+
'left: 0 !important; top: 0 !important;' +
|
|
380
|
+
'width: 100% !important; max-width: none !important;' +
|
|
381
|
+
'margin: 0 !important;' +
|
|
382
|
+
'}' +
|
|
383
|
+
'[data-no-export] { display: none !important; }' +
|
|
384
|
+
'}';
|
|
385
|
+
document.head.appendChild(style);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const cleanup = () => {
|
|
389
|
+
document.title = original;
|
|
390
|
+
if (style) style.remove();
|
|
391
|
+
if (injectedId && target) target.removeAttribute('id');
|
|
392
|
+
window.removeEventListener('afterprint', onAfter);
|
|
393
|
+
};
|
|
394
|
+
const onAfter = () => cleanup();
|
|
395
|
+
window.addEventListener('afterprint', onAfter);
|
|
396
|
+
|
|
347
397
|
try { window.print(); }
|
|
348
|
-
|
|
349
|
-
//
|
|
350
|
-
|
|
398
|
+
catch (err) {
|
|
399
|
+
// If print() throws synchronously (rare), tear down immediately.
|
|
400
|
+
cleanup();
|
|
401
|
+
throw err;
|
|
402
|
+
}
|
|
403
|
+
// Some browsers don't fire afterprint reliably (e.g. when the user
|
|
404
|
+
// cancels with Esc in older Safari). Schedule a fallback teardown
|
|
405
|
+
// a few seconds out so we don't leak the title swap or the style tag.
|
|
406
|
+
setTimeout(cleanup, 30000);
|
|
407
|
+
}
|
|
408
|
+
|
|
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));
|
|
351
415
|
}
|
|
416
|
+
return String(value).replace(/[^a-zA-Z0-9_-]/g, (c) => '\\' + c);
|
|
352
417
|
}
|
|
353
418
|
|
|
354
419
|
function effectivePageBackground() {
|
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
"manifest.appwrite.auth.js": "sha384-Kvv9SjOFBFY2LELQunQpKLr3uDT+supgouo93eahEL4dAHntq0F9u8Hoyx85eQKr",
|
|
3
3
|
"manifest.appwrite.data.js": "sha384-00ulLT+GAIuPHA/rRT9p98vYlsyDzkyKXtg86BDQ6FGQa5vVVN+W6kuforniBAsz",
|
|
4
4
|
"manifest.appwrite.presence.js": "sha384-uxRpx9/Jj0kGtklH5QmUlAzD3zdSvFRfK6bcJQqxl+Bsf5tOo4zgwqJTQgtZoHQP",
|
|
5
|
-
"manifest.code.js": "sha384-
|
|
5
|
+
"manifest.code.js": "sha384-0uQckfvrEyDVV1Er8SpciZM5egnBzgJG4QFl7ZNdFOWiNLkENeLcRjJKzPLlQO6G",
|
|
6
6
|
"manifest.color.js": "sha384-Z9G/lzt0vVMxjz4wkPuGG1X9mmQAJR15aOoGX3ephf7r2wnlUWet5GLgkUMtT4vt",
|
|
7
7
|
"manifest.colorpicker.js": "sha384-0EVn+Ha06h7FIvOxc6WjZYnKYXzi+zba08yKvczSEGTRkWRxyKN2TFrZHI1SDCXu",
|
|
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-kTZBHsXPtj6RuLSyR323lCjIm0VTtZ64IdpyvOULMMJHqw/kIXjtQDyph5Ak/Ryv",
|
|
12
12
|
"manifest.icons.js": "sha384-uOkboYrovjCpl22eey3Jaxpey+pOnot5NDnRRumcRxiR7IOVaRh1i20gYnWXR5dW",
|
|
13
13
|
"manifest.localization.js": "sha384-M40EWrbWs2MoJvUiYVQaPxPiOzMYBn/ywuMR02rvoSXG77eIHT/aRoYub9r6+jC+",
|
|
14
|
-
"manifest.markdown.js": "sha384-
|
|
14
|
+
"manifest.markdown.js": "sha384-jJFVCg46bYcP/xglX6QguG2qsGi2I/0o9kXLpM2RMq7edbTYpgx6WQ9iNB7kKHmN",
|
|
15
15
|
"manifest.resize.js": "sha384-Ak5gf44ERfh9pOSAD1qZzJSysslpwBCkevIlz7R3dszTUyzUKGKGF4pn5arOtgG0",
|
|
16
|
-
"manifest.router.js": "sha384
|
|
16
|
+
"manifest.router.js": "sha384-/JWAmVtxin3AKAdtIgXLjilMDj9RztBFn1W/ZSu/OPngfj2K8f3oYvz4UKXLwBRm",
|
|
17
17
|
"manifest.slides.js": "sha384-3uRTkyK9XPLmnxI2+igZlpi4EyPlU/7IHj5j3BZJJ2KN455vXyk99fiXV3feO/XY",
|
|
18
18
|
"manifest.svg.js": "sha384-nc+3spSGNS2l+82maL/OFz2iOGUhLZ0kqeooj28CEcdElM4OZa34e0tbnokZHVI6",
|
|
19
19
|
"manifest.tabs.js": "sha384-v6Ti0zHfdLhkFHbTMg0FH6uMrThuBvZrL2PQgVBeeXhDjuN7x4MtoNWogPbAQTaD",
|
package/lib/manifest.markdown.js
CHANGED
|
@@ -375,7 +375,16 @@ function applyInlineCodeAttributes(html) {
|
|
|
375
375
|
language = tok;
|
|
376
376
|
}
|
|
377
377
|
}
|
|
378
|
-
|
|
378
|
+
// Only emit x-code when the author actually requested a language.
|
|
379
|
+
// `inline`{copy} should produce `<code copy>` — copy is a UI flag,
|
|
380
|
+
// not a request for syntax highlighting. Previously we always
|
|
381
|
+
// wrote `x-code=""`, which the code plugin treated as auto-detect
|
|
382
|
+
// and ran hljs.highlightElement on the codespan, colouring
|
|
383
|
+
// identifiers like `x-code` themselves as tokens. Authors who
|
|
384
|
+
// want highlighting opt in explicitly via `code`{bash} or by
|
|
385
|
+
// hand-writing `<code x-code="bash">…</code>`.
|
|
386
|
+
let attrs = '';
|
|
387
|
+
if (language) attrs += ` x-code="${escapeForAttribute(language)}"`;
|
|
379
388
|
for (const flag of flags) attrs += ` ${flag}`;
|
|
380
389
|
if (classes.length) attrs += ` class="${escapeForAttribute(classes.join(' '))}"`;
|
|
381
390
|
for (const [k, v] of kv) attrs += ` ${k}="${escapeForAttribute(v)}"`;
|
package/lib/manifest.router.js
CHANGED
|
@@ -587,6 +587,20 @@ function interceptLinkClicks() {
|
|
|
587
587
|
// Handle pure anchor links normally - don't intercept them
|
|
588
588
|
if (href.startsWith('#')) return;
|
|
589
589
|
|
|
590
|
+
// Don't intercept blob: or data: URLs. The export plugin creates a
|
|
591
|
+
// throwaway <a href="blob:…" download="…"> and programmatically
|
|
592
|
+
// clicks it to trigger a file save; if the router treats it as a
|
|
593
|
+
// SPA link, `new URL("blob:http://localhost/UUID", origin).pathname`
|
|
594
|
+
// resolves to "http://localhost/UUID" which then pushState pushes
|
|
595
|
+
// as a same-origin path, producing /http://localhost/UUID and
|
|
596
|
+
// landing the user on a 404 instead of downloading.
|
|
597
|
+
if (href.startsWith('blob:') || href.startsWith('data:')) return;
|
|
598
|
+
|
|
599
|
+
// Honor `download` — the link is opting out of SPA navigation in
|
|
600
|
+
// favor of letting the browser save its target. Same intent as
|
|
601
|
+
// blob:/data:, just expressed via the standard HTML attribute.
|
|
602
|
+
if (link.hasAttribute('download')) return;
|
|
603
|
+
|
|
590
604
|
// Check if it's an external link FIRST (before any other processing)
|
|
591
605
|
let isExternalLink = false;
|
|
592
606
|
try {
|