From 1a2d817cf945303f4ce8e7547216131ec15a3224 Mon Sep 17 00:00:00 2001 From: Server <10.9.8.250@reg.snw.su> Date: Mon, 10 Aug 2026 22:42:46 +0000 Subject: [PATCH] Dispatch manifest update event --- README.md | 24 ++++++++++++++++++++++++ docslib/__init__.py | 2 +- docslib/templates/client.js | 33 +++++++++++++++++++++++++++------ examples/assets/inject.js | 4 ++++ pyproject.toml | 2 +- tests/test_server.py | 8 +++++++- uv.lock | 2 +- 7 files changed, 65 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 1b93c6a..d83a72c 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,30 @@ document.addEventListener("docslib:rendered", (event) => { }, { once: true }); ``` +При обнаружении новой ревизии клиент отправляет всплывающее DOM-событие `on_manifest_update`. Обработчик из `custom_js` регистрируется до начала фоновой проверки: + +```javascript +document.addEventListener("on_manifest_update", (event) => { + const { + manifest, + currentManifest, + revision, + previousRevision, + source, + cached, + applied, + } = event.detail; + + console.log(`Manifest ${previousRevision} → ${revision}`, { + manifest, + currentManifest, + source, // "remote" или "embedded" + cached, // удалось ли записать обновление в IndexedDB + applied, // false: оно применится при следующем открытии + }); +}); +``` + Код из `custom_js` считается доверенным и выполняется в контексте страницы. Изменение системного или пользовательского CSS/JS меняет `revision`, поэтому обновлённый runtime сохраняется в IndexedDB и применяется после перезагрузки клиента. ## Live-preview diff --git a/docslib/__init__.py b/docslib/__init__.py index 42c523c..523f1d3 100644 --- a/docslib/__init__.py +++ b/docslib/__init__.py @@ -5,4 +5,4 @@ from .server import build, create_app, run from .ui import HeaderButton __all__ = ["Component", "HeaderButton", "Page", "build", "create_app", "run"] -__version__ = "0.3.4" +__version__ = "0.3.5" diff --git a/docslib/templates/client.js b/docslib/templates/client.js index 20bab9f..65980f2 100644 --- a/docslib/templates/client.js +++ b/docslib/templates/client.js @@ -442,6 +442,28 @@ } } + async function announceManifestUpdate(nextManifest, source) { + const previousRevision = lastSyncedRevision; + let cached = false; + try { + await cachePut(nextManifest); + cached = true; + } catch (_) { /* the update event still reports an unavailable cache */ } + lastSyncedRevision = String(nextManifest.revision || lastSyncedRevision); + document.dispatchEvent(new CustomEvent("on_manifest_update", { + bubbles: true, + detail: { + manifest: nextManifest, + currentManifest: manifest, + revision: nextManifest.revision, + previousRevision, + source, + cached, + applied: false, + }, + })); + } + async function syncManifest() { if (syncing) return false; setSyncing(true); @@ -450,11 +472,11 @@ if ( isManifest(embedded) && embedded.schema_version === manifest.schema_version && - String(embedded.updated_at || "") > String(manifest.updated_at || "") + String(embedded.updated_at || "") > String(manifest.updated_at || "") && + embedded.revision !== lastSyncedRevision ) { - await cachePut(embedded).catch(() => {}); - lastSyncedRevision = String(embedded.revision || lastSyncedRevision); - changed = embedded.revision !== manifest.revision; + await announceManifestUpdate(embedded, "embedded"); + changed = true; } const response = await fetch(config.manifestUrl, { cache: "no-store" }); if (!response.ok) throw new Error(String(response.status)); @@ -462,8 +484,7 @@ if (!isManifest(remote) || remote.schema_version !== embedded.schema_version) throw new Error("manifest"); const remoteChanged = remote.revision !== lastSyncedRevision; if (remoteChanged) { - await cachePut(remote).catch(() => {}); - lastSyncedRevision = String(remote.revision || lastSyncedRevision); + await announceManifestUpdate(remote, "remote"); changed = true; } elements.refresh.classList.remove("error"); diff --git a/examples/assets/inject.js b/examples/assets/inject.js index 7e011ff..13bfb96 100644 --- a/examples/assets/inject.js +++ b/examples/assets/inject.js @@ -1,3 +1,7 @@ document.addEventListener("docslib:rendered", (event) => { document.documentElement.dataset.exampleRevision = event.detail.manifest.revision; }, { once: true }); + +document.addEventListener("on_manifest_update", (event) => { + document.documentElement.dataset.pendingRevision = event.detail.revision; +}); diff --git a/pyproject.toml b/pyproject.toml index b0e1eec..14f3e5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "shared-docs-lib" -version = "0.3.4" +version = "0.3.5" description = "Build self-contained, Obsidian-like documentation sites from Python functions." readme = "README.md" requires-python = ">=3.10" diff --git a/tests/test_server.py b/tests/test_server.py index c8cd177..ad639cc 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -95,6 +95,12 @@ def test_all_endpoints_and_self_contained_build(tmp_path): assert "return cached" in html assert 'id="docslib-loader-style"' in html assert "docslib:rendered" in html + system_js = manifest["client"]["js"]["system"] + assert 'new CustomEvent("on_manifest_update"' in system_js + assert "currentManifest: manifest" in system_js + assert "previousRevision" in system_js + assert "cached," in system_js + assert "applied: false" in system_js assert "docslib-runtime-style" in html assert "indexedDB.open" in html assert "link.dataset.page = button.page" in html @@ -168,7 +174,7 @@ def test_all_endpoints_and_self_contained_build(tmp_path): def test_build_writes_static_bundle(tmp_path): - assert __version__ == "0.3.4" + assert __version__ == "0.3.5" @Page("1", "Static page") def static_page(): diff --git a/uv.lock b/uv.lock index cfafea0..39688fb 100644 --- a/uv.lock +++ b/uv.lock @@ -452,7 +452,7 @@ wheels = [ [[package]] name = "shared-docs-lib" -version = "0.3.4" +version = "0.3.5" source = { editable = "." } dependencies = [ { name = "fastapi" },