Wait for all images before printing

This commit is contained in:
Server 2026-08-12 09:30:15 +00:00
parent c489209610
commit c72f5fed82
5 changed files with 91 additions and 34 deletions

View file

@ -260,7 +260,7 @@ console.log(globalThis.COMPILED_FROM_VERSION); // например, "0.3.7"
PrintFullDocumentation();
```
Она формирует единый документ из всех страниц в порядке manifest и открывает системный диалог печати. Каждая страница документации начинается с нового печатного листа. Печатная тема всегда использует белый фон и чёрный текст независимо от выбранной экранной темы.
Она сразу закрывает интерфейс тематическим loading-overlay, формирует единый документ из всех страниц в порядке manifest, подставляет и декодирует все изображения, ожидает готовности шрифтов и только затем открывает системный диалог печати. Каждая страница документации начинается с нового печатного листа. Печатная тема всегда использует белый фон, чёрный текст и компактные заголовки независимо от выбранной экранной темы.
## Live-preview

View file

@ -1,3 +1,3 @@
"""Single source of truth for the SharedDocsLib package version."""
__version__ = "0.3.8"
__version__ = "0.3.9"

View file

@ -135,6 +135,8 @@ button { color: inherit; }
.mobile-view, .mobile-dock { display: none; }
.full-print-document { display: none; }
.print-loading-overlay { position: fixed; inset: 0; z-index: 2147483647; display: grid; width: 100vw; height: 100dvh; place-items: center; color: var(--text); background: var(--shell); }
.print-loading-spinner { width: 48px; height: 48px; box-sizing: border-box; border: solid 4px currentColor; border-top-color: transparent; border-radius: 9999px; animation: refresh-spin .8s linear infinite; }
@media (max-width: 820px) {
.app-shell { display: block; }
@ -186,7 +188,7 @@ button { color: inherit; }
.content a, .content figcaption, .content blockquote { color: #000 !important; }
.content pre, .content code, .content details, .content th, .callout { color: #000 !important; background: #fff !important; border-color: #777 !important; }
.app-shell { display: block; }
.desktop-header, .desktop-sidebar, .mobile-dock, .page-navigation { display: none; }
.desktop-header, .desktop-sidebar, .mobile-dock, .page-navigation, .print-loading-overlay { display: none !important; }
.workspace, .page-surface { display: block; height: auto; overflow: visible; }
.content { width: 100%; padding: 0; }
body.docslib-print-all > .app-shell { display: none; }
@ -194,6 +196,9 @@ button { color: inherit; }
.print-page { break-after: page; page-break-after: always; }
.print-page:last-child { break-after: auto; page-break-after: auto; }
.print-page-heading { margin: 0 0 2rem; padding-bottom: 1rem; border-bottom: 1px solid #777; }
.print-page-heading h1 { margin: 0; color: #000 !important; }
.print-page-heading h1 { margin: 0; color: #000 !important; font-size: 1.35rem !important; line-height: 1.25; }
.print-page-number { display: block; margin-bottom: .3rem; color: #555 !important; }
.print-page-content h1 { font-size: 1.8rem !important; line-height: 1.25; }
.print-page-content h2 { font-size: 1.4rem !important; line-height: 1.3; }
.print-page-content h3 { font-size: 1.15rem !important; line-height: 1.35; }
}

View file

@ -226,11 +226,14 @@
return asset ? `data:${asset.mime};base64,${asset.data}` : "";
}
function hydrateAssets(root = elements.content) {
function hydrateAssets(root = elements.content, { eager = false } = {}) {
root.querySelectorAll("[data-docslib-asset]").forEach((element) => {
const name = element.dataset.docslibAsset;
const uri = assetDataUri(name);
if (uri && element.tagName === "IMG") element.src = uri;
if (uri && element.tagName === "IMG") {
if (eager) element.loading = "eager";
element.src = uri;
}
else if (!uri) element.setAttribute("title", name);
});
}
@ -240,37 +243,76 @@
document.getElementById("docslib-full-print")?.remove();
}
function PrintFullDocumentation() {
function showPrintLoading() {
document.getElementById("docslib-print-loading")?.remove();
const overlay = document.createElement("div");
overlay.id = "docslib-print-loading";
overlay.className = "print-loading-overlay";
overlay.setAttribute("aria-hidden", "true");
const spinner = document.createElement("div");
spinner.className = "print-loading-spinner";
overlay.append(spinner);
document.body.append(overlay);
return overlay;
}
async function waitForPrintAssets(root) {
const images = [...root.querySelectorAll("img")];
await Promise.all(images.map(async (image) => {
if (!image.complete) {
await new Promise((resolve) => {
image.addEventListener("load", resolve, { once: true });
image.addEventListener("error", resolve, { once: true });
});
}
if (typeof image.decode === "function" && image.naturalWidth > 0) {
await image.decode().catch(() => {});
}
}));
if (document.fonts && document.fonts.ready) await document.fonts.ready;
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
}
async function PrintFullDocumentation() {
const overlay = showPrintLoading();
removeFullPrintDocument();
const printable = document.createElement("div");
printable.id = "docslib-full-print";
printable.className = "full-print-document";
try {
const printable = document.createElement("div");
printable.id = "docslib-full-print";
printable.className = "full-print-document";
manifest.pages.forEach((page) => {
const article = document.createElement("article");
article.className = "content print-page";
article.dataset.page = page.number;
manifest.pages.forEach((page) => {
const article = document.createElement("article");
article.className = "content print-page";
article.dataset.page = page.number;
const heading = document.createElement("header");
heading.className = "print-page-heading";
const number = document.createElement("span");
number.className = "print-page-number";
number.textContent = page.number;
const title = document.createElement("h1");
title.textContent = page.title;
heading.append(number, title);
const heading = document.createElement("header");
heading.className = "print-page-heading";
const number = document.createElement("span");
number.className = "print-page-number";
number.textContent = page.number;
const title = document.createElement("h1");
title.textContent = page.title;
heading.append(number, title);
const body = document.createElement("div");
body.className = "print-page-content";
body.innerHTML = page.content || "";
article.append(heading, body);
hydrateAssets(article);
printable.append(article);
});
const body = document.createElement("div");
body.className = "print-page-content";
body.innerHTML = page.content || "";
article.append(heading, body);
hydrateAssets(article, { eager: true });
printable.append(article);
});
document.body.append(printable);
document.body.classList.add("docslib-print-all");
requestAnimationFrame(() => requestAnimationFrame(() => window.print()));
document.body.append(printable);
await waitForPrintAssets(printable);
document.body.classList.add("docslib-print-all");
window.print();
} catch (error) {
removeFullPrintDocument();
throw error;
} finally {
overlay.remove();
}
}
globalThis.PrintFullDocumentation = PrintFullDocumentation;

View file

@ -97,7 +97,7 @@ def test_all_endpoints_and_self_contained_build(tmp_path):
assert html_response.status_code == 200
assert "http://127.0.0.1:9000/manifest.json" in html
assert "http://127.0.0.1:9000/version" in html
assert 'globalThis.COMPILED_FROM_VERSION = "0.3.8";' in html
assert 'globalThis.COMPILED_FROM_VERSION = "0.3.9";' in html
assert ".content { --custom-test: yes; }" in html
assert r'document.body.dataset.customTest = \"yes\";' in html
assert 'id="docslib-loading"' in html
@ -160,10 +160,20 @@ def test_all_endpoints_and_self_contained_build(tmp_path):
assert 'printable.id = "docslib-full-print"' in system_js
assert "manifest.pages.forEach((page) =>" in system_js
assert 'window.addEventListener("afterprint", removeFullPrintDocument)' in system_js
assert "async function PrintFullDocumentation()" in system_js
assert "await waitForPrintAssets(printable)" in system_js
assert 'if (eager) element.loading = "eager"' in system_js
assert "hydrateAssets(article, { eager: true })" in system_js
assert 'image.addEventListener("load", resolve, { once: true })' in system_js
assert 'await image.decode().catch(() => {})' in system_js
assert "await document.fonts.ready" in system_js
system_css = manifest["client"]["css"]["system"]
assert 'body.docslib-print-all > .full-print-document { display: block; }' in system_css
assert 'color: #000 !important; background: #fff !important;' in system_css
assert ".print-page { break-after: page; page-break-after: always; }" in system_css
assert ".print-loading-overlay { position: fixed; inset: 0; z-index: 2147483647; display: grid; width: 100vw; height: 100dvh" in system_css
assert ".print-page-heading h1 { margin: 0; color: #000 !important; font-size: 1.35rem !important" in system_css
assert ".print-page-content h1 { font-size: 1.8rem !important" in system_css
assert "--shell: #000000" in html
assert 'id="desktop-search-input"' in html
assert '<header class="desktop-header">' in html
@ -208,7 +218,7 @@ def test_all_endpoints_and_self_contained_build(tmp_path):
def test_build_writes_static_bundle(tmp_path):
assert __version__ == "0.3.8"
assert __version__ == "0.3.9"
@Page("1", "Static page")
def static_page():