diff --git a/README.md b/README.md index ee7f4c2..b88525a 100644 --- a/README.md +++ b/README.md @@ -254,6 +254,14 @@ console.log(globalThis.COMPILED_FROM_VERSION); // например, "0.3.7" Присваивание добавляется в bootstrap-скрипт самой HTML-ки. Автообновление manifest не меняет `COMPILED_FROM_VERSION`: значение обновится только после новой компиляции и замены `index.html`. +Для печати всей документации из `custom_js` или консоли доступна глобальная функция: + +```javascript +PrintFullDocumentation(); +``` + +Она формирует единый документ из всех страниц в порядке manifest и открывает системный диалог печати. Каждая страница документации начинается с нового печатного листа. Печатная тема всегда использует белый фон и чёрный текст независимо от выбранной экранной темы. + ## Live-preview В live-режиме браузер опрашивает только `/live`. Сервер следит за файлами, в которых объявлены страницы, а также за системными и пользовательскими CSS/JS. При изменении он пересобирает manifest; клиент сохраняет его и перезагружается с новым runtime. В запускаемом файле вызов `run()` обязательно помещать под `if __name__ == "__main__"`, чтобы watcher не запускал второй сервер. diff --git a/docslib/_version.py b/docslib/_version.py index ae36319..84cd7c2 100644 --- a/docslib/_version.py +++ b/docslib/_version.py @@ -1,4 +1,3 @@ """Single source of truth for the SharedDocsLib package version.""" -__version__ = "0.3.7" - +__version__ = "0.3.8" diff --git a/docslib/templates/client.css b/docslib/templates/client.css index ddb42a1..38a646e 100644 --- a/docslib/templates/client.css +++ b/docslib/templates/client.css @@ -134,6 +134,7 @@ button { color: inherit; } :root[data-theme="dark"] #next-page { color: #000; background: #eee; } .mobile-view, .mobile-dock { display: none; } +.full-print-document { display: none; } @media (max-width: 820px) { .app-shell { display: block; } @@ -179,9 +180,20 @@ button { color: inherit; } } @media print { - html, body { height: auto; max-height: none; overflow: visible; } + :root, :root[data-theme="dark"] { color-scheme: light; } + html, body { height: auto; max-height: none; overflow: visible; color: #000 !important; background: #fff !important; } + .app-shell, .workspace, .page-surface, .content, .full-print-document, .print-page { color: #000 !important; background: #fff !important; } + .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; } .workspace, .page-surface { display: block; height: auto; overflow: visible; } .content { width: 100%; padding: 0; } + body.docslib-print-all > .app-shell { display: none; } + body.docslib-print-all > .full-print-document { display: block; } + .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-number { display: block; margin-bottom: .3rem; color: #555 !important; } } diff --git a/docslib/templates/client.js b/docslib/templates/client.js index eb3541e..df90dd2 100644 --- a/docslib/templates/client.js +++ b/docslib/templates/client.js @@ -226,8 +226,8 @@ return asset ? `data:${asset.mime};base64,${asset.data}` : ""; } - function hydrateAssets() { - elements.content.querySelectorAll("[data-docslib-asset]").forEach((element) => { + function hydrateAssets(root = elements.content) { + root.querySelectorAll("[data-docslib-asset]").forEach((element) => { const name = element.dataset.docslibAsset; const uri = assetDataUri(name); if (uri && element.tagName === "IMG") element.src = uri; @@ -235,6 +235,47 @@ }); } + function removeFullPrintDocument() { + document.body.classList.remove("docslib-print-all"); + document.getElementById("docslib-full-print")?.remove(); + } + + function PrintFullDocumentation() { + removeFullPrintDocument(); + 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; + + 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); + }); + + document.body.append(printable); + document.body.classList.add("docslib-print-all"); + requestAnimationFrame(() => requestAnimationFrame(() => window.print())); + } + + globalThis.PrintFullDocumentation = PrintFullDocumentation; + window.addEventListener("afterprint", removeFullPrintDocument); + function updateActiveNavigation(number) { document.querySelectorAll(".tree-link[data-page]").forEach((button) => { button.classList.toggle("active", button.dataset.page === number); diff --git a/tests/test_server.py b/tests/test_server.py index 5662c22..23b7189 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -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.7";' in html + assert 'globalThis.COMPILED_FROM_VERSION = "0.3.8";' in html assert ".content { --custom-test: yes; }" in html assert r'document.body.dataset.customTest = \"yes\";' in html assert 'id="docslib-loading"' in html @@ -156,6 +156,14 @@ def test_all_endpoints_and_self_contained_build(tmp_path): assert ".nav-arrow-previous { border-bottom-width: 2px; border-left-width: 2px; }" in html assert ".nav-arrow-next { border-top-width: 2px; border-right-width: 2px; }" in html assert 'arrow.textContent = direction' not in html + assert "globalThis.PrintFullDocumentation = PrintFullDocumentation" in system_js + 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 + 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 "--shell: #000000" in html assert 'id="desktop-search-input"' in html assert '
' in html @@ -200,7 +208,7 @@ def test_all_endpoints_and_self_contained_build(tmp_path): def test_build_writes_static_bundle(tmp_path): - assert __version__ == "0.3.7" + assert __version__ == "0.3.8" @Page("1", "Static page") def static_page():