Animate navigation and sidebar controls
This commit is contained in:
parent
c72f5fed82
commit
624f62f25e
6 changed files with 98 additions and 19 deletions
|
|
@ -15,9 +15,9 @@ SharedDocsLib — небольшой Python-фреймворк для докум
|
||||||
|
|
||||||
UI не содержит языко-зависимых служебных подписей. Базовый шрифт — `system-ui 16px`, а служебные UTF-8-пиктограммы используют Arial. Навигационные стрелки отрисовываются CSS-границами без текстовых символов. AMOLED-тема включена по умолчанию и сохраняется локально после переключения.
|
UI не содержит языко-зависимых служебных подписей. Базовый шрифт — `system-ui 16px`, а служебные UTF-8-пиктограммы используют Arial. Навигационные стрелки отрисовываются CSS-границами без текстовых символов. AMOLED-тема включена по умолчанию и сохраняется локально после переключения.
|
||||||
|
|
||||||
На десктопе компактный header содержит полное название документации, поиск и переключатель темы, а слева постоянно видны header-кнопки и дерево страниц. Поисковый placeholder медленно печатает первые слова случайного названия, добавляет троеточие только в конце и сохраняет готовый текст 5 секунд; при вводе поле раскрывает ранжированные результаты вниз. Проверка manifest выполняется автоматически в фоне без отдельной кнопки обновления.
|
На десктопе компактный header содержит toggle боковой панели, полное название документации, поиск и переключатель темы. Sidebar по умолчанию открыт, его состояние не сохраняется между загрузками. Поисковый placeholder медленно печатает первые слова случайного названия, добавляет троеточие только в конце и сохраняет готовый текст 5 секунд; при вводе поле раскрывает ранжированные результаты вниз. Проверка manifest выполняется автоматически в фоне без отдельной кнопки обновления.
|
||||||
|
|
||||||
На мобильных устройствах используется viewport `100dvh` и нижний dock. Кнопка `☰` заменяет содержимое страницы копией desktop-sidebar. Ввод в нижний поиск заменяет содержимое результатами, расположенными снизу вверх по релевантности. Тема и обновление на мобильном скрыты.
|
На мобильных устройствах используется viewport `100dvh` и нижний dock. Кнопка `☰` заменяет содержимое страницы копией desktop-sidebar, которая выезжает с левой границы экрана. Ввод в нижний поиск заменяет содержимое результатами, расположенными снизу вверх по релевантности. При переходе между страницами контент плавно смещается на 30 px по направлению навигации и меняет прозрачность; тема и обновление на мобильном скрыты.
|
||||||
|
|
||||||
## Установка из Git
|
## Установка из Git
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
"""Single source of truth for the SharedDocsLib package version."""
|
"""Single source of truth for the SharedDocsLib package version."""
|
||||||
|
|
||||||
__version__ = "0.3.9"
|
__version__ = "0.4.0"
|
||||||
|
|
|
||||||
|
|
@ -46,13 +46,17 @@ button { color: inherit; }
|
||||||
|
|
||||||
.app-shell { --desktop-sidebar-width: clamp(270px, 25vw, 400px); display: flex; width: 100%; height: 100%; flex-direction: column; overflow: hidden; background: var(--shell); }
|
.app-shell { --desktop-sidebar-width: clamp(270px, 25vw, 400px); display: flex; width: 100%; height: 100%; flex-direction: column; overflow: hidden; background: var(--shell); }
|
||||||
.desktop-header { position: relative; z-index: 20; display: grid; min-height: 76px; flex: 0 0 76px; grid-template-columns: minmax(260px, 1fr) auto; align-items: center; gap: 18px; padding: 12px 20px; background: var(--shell); }
|
.desktop-header { position: relative; z-index: 20; display: grid; min-height: 76px; flex: 0 0 76px; grid-template-columns: minmax(260px, 1fr) auto; align-items: center; gap: 18px; padding: 12px 20px; background: var(--shell); }
|
||||||
|
.desktop-title-group { display: flex; min-width: 0; align-items: center; gap: 12px; }
|
||||||
.desktop-title { overflow: hidden; margin: 0; font-size: 1.55rem; font-weight: 450; line-height: 1.1; letter-spacing: -.025em; text-overflow: ellipsis; white-space: nowrap; }
|
.desktop-title { overflow: hidden; margin: 0; font-size: 1.55rem; font-weight: 450; line-height: 1.1; letter-spacing: -.025em; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.desktop-sidebar-toggle { display: grid; width: 44px; height: 44px; flex: 0 0 44px; place-items: center; border-radius: 50%; color: var(--text); background: var(--control); font-size: 1.6rem; }
|
||||||
|
.desktop-sidebar-toggle:hover { background: var(--active); }
|
||||||
.header-link { display: block; width: 100%; padding: 6px 12px; border: 0; border-radius: 20px; color: var(--text); background: transparent; text-decoration: none; line-height: 1.2; transition: background .16s ease; }
|
.header-link { display: block; width: 100%; padding: 6px 12px; border: 0; border-radius: 20px; color: var(--text); background: transparent; text-decoration: none; line-height: 1.2; transition: background .16s ease; }
|
||||||
.header-link:hover { background: var(--control); }
|
.header-link:hover { background: var(--control); }
|
||||||
.header-link.active { color: var(--text); background: var(--active); }
|
.header-link.active { color: var(--text); background: var(--active); }
|
||||||
|
|
||||||
.desktop-main { display: flex; min-width: 0; min-height: 0; flex: 1; overflow: hidden; }
|
.desktop-main { display: flex; min-width: 0; min-height: 0; flex: 1; overflow: hidden; }
|
||||||
.desktop-sidebar { display: flex; width: var(--desktop-sidebar-width); min-width: 0; height: 100%; flex: 0 0 var(--desktop-sidebar-width); flex-direction: column; overflow: hidden; padding: 0 18px; background: var(--shell); }
|
.desktop-sidebar { display: flex; width: var(--desktop-sidebar-width); min-width: 0; height: 100%; flex: 0 0 var(--desktop-sidebar-width); flex-direction: column; overflow: hidden; padding: 0 18px; background: var(--shell); opacity: 1; transition: width .24s ease, flex-basis .24s ease, padding .24s ease, opacity .16s ease; }
|
||||||
|
body.desktop-sidebar-collapsed .desktop-sidebar { width: 0; flex-basis: 0; padding-right: 0; padding-left: 0; opacity: 0; pointer-events: none; }
|
||||||
.sidebar-header-buttons { display: flex; flex: 0 0 auto; flex-direction: column; gap: 4px; margin-bottom: 0; border: 1px solid var(--border); border-radius: 16px; background: var(--surface); }
|
.sidebar-header-buttons { display: flex; flex: 0 0 auto; flex-direction: column; gap: 4px; margin-bottom: 0; border: 1px solid var(--border); border-radius: 16px; background: var(--surface); }
|
||||||
.sidebar-header-buttons .header-link { color: var(--text); }
|
.sidebar-header-buttons .header-link { color: var(--text); }
|
||||||
.sidebar-header-buttons .header-link:hover { background: var(--control); }
|
.sidebar-header-buttons .header-link:hover { background: var(--control); }
|
||||||
|
|
@ -157,6 +161,7 @@ button { color: inherit; }
|
||||||
body.mobile-menu-open .mobile-menu-view { display: block; }
|
body.mobile-menu-open .mobile-menu-view { display: block; }
|
||||||
body.mobile-search-open .mobile-search-view { display: flex; }
|
body.mobile-search-open .mobile-search-view { display: flex; }
|
||||||
.mobile-menu-view { padding: 52px 28px calc(150px + env(safe-area-inset-bottom)); }
|
.mobile-menu-view { padding: 52px 28px calc(150px + env(safe-area-inset-bottom)); }
|
||||||
|
body.mobile-menu-open .mobile-menu-view { animation: mobile-menu-enter .26s cubic-bezier(.2, .8, .2, 1) both; }
|
||||||
.mobile-site-title { margin: 0 0 34px; font-size: clamp(2.4rem, 11vw, 4rem); font-weight: 400; line-height: 1.1; letter-spacing: -.035em; overflow-wrap: anywhere; }
|
.mobile-site-title { margin: 0 0 34px; font-size: clamp(2.4rem, 11vw, 4rem); font-weight: 400; line-height: 1.1; letter-spacing: -.035em; overflow-wrap: anywhere; }
|
||||||
.mobile-header-buttons { display: flex; flex-direction: column; gap: 6px; margin-bottom: 38px; border: 1px solid var(--border); border-radius: 24px; background: var(--surface); }
|
.mobile-header-buttons { display: flex; flex-direction: column; gap: 6px; margin-bottom: 38px; border: 1px solid var(--border); border-radius: 24px; background: var(--surface); }
|
||||||
.mobile-header-buttons .header-link { padding: 12px 18px; border-radius: 28px; font-size: 1.05rem; }
|
.mobile-header-buttons .header-link { padding: 12px 18px; border-radius: 28px; font-size: 1.05rem; }
|
||||||
|
|
@ -177,6 +182,11 @@ button { color: inherit; }
|
||||||
.mobile-search-control .search-glyph { width: 64px; height: 64px; flex-basis: 64px; background: transparent; font-size: 2.3rem; }
|
.mobile-search-control .search-glyph { width: 64px; height: 64px; flex-basis: 64px; background: transparent; font-size: 2.3rem; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes mobile-menu-enter {
|
||||||
|
from { opacity: 0; transform: translateX(-100%); }
|
||||||
|
to { opacity: 1; transform: translateX(0); }
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
*, *::before, *::after { scroll-behavior: auto !important; transition-duration: .01ms !important; animation-duration: .01ms !important; animation-iteration-count: 1 !important; }
|
*, *::before, *::after { scroll-behavior: auto !important; transition-duration: .01ms !important; animation-duration: .01ms !important; animation-iteration-count: 1 !important; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)");
|
const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)");
|
||||||
const elements = {
|
const elements = {
|
||||||
desktopTitle: document.getElementById("desktop-site-title"),
|
desktopTitle: document.getElementById("desktop-site-title"),
|
||||||
|
desktopSidebarToggle: document.getElementById("desktop-sidebar-toggle"),
|
||||||
mobileTitle: document.getElementById("mobile-site-title"),
|
mobileTitle: document.getElementById("mobile-site-title"),
|
||||||
desktopHeaderButtons: document.getElementById("desktop-header-buttons"),
|
desktopHeaderButtons: document.getElementById("desktop-header-buttons"),
|
||||||
mobileHeaderButtons: document.getElementById("mobile-header-buttons"),
|
mobileHeaderButtons: document.getElementById("mobile-header-buttons"),
|
||||||
|
|
@ -44,6 +45,8 @@
|
||||||
let lastPlaceholderPage = -1;
|
let lastPlaceholderPage = -1;
|
||||||
let customScriptLoaded = false;
|
let customScriptLoaded = false;
|
||||||
let lastSyncedRevision = String(manifest.revision || "");
|
let lastSyncedRevision = String(manifest.revision || "");
|
||||||
|
let pageTransitionId = 0;
|
||||||
|
let pageAnimation = null;
|
||||||
|
|
||||||
const dbPromise = new Promise((resolve, reject) => {
|
const dbPromise = new Promise((resolve, reject) => {
|
||||||
if (!window.indexedDB) return reject(new Error("IndexedDB"));
|
if (!window.indexedDB) return reject(new Error("IndexedDB"));
|
||||||
|
|
@ -135,6 +138,11 @@
|
||||||
document.body.classList.toggle("mobile-search-open", mode === "search");
|
document.body.classList.toggle("mobile-search-open", mode === "search");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setDesktopSidebar(open) {
|
||||||
|
document.body.classList.toggle("desktop-sidebar-collapsed", !open);
|
||||||
|
elements.desktopSidebarToggle.setAttribute("aria-expanded", String(open));
|
||||||
|
}
|
||||||
|
|
||||||
function closeSearch({ clear = false } = {}) {
|
function closeSearch({ clear = false } = {}) {
|
||||||
elements.desktopSearchShell.classList.remove("open");
|
elements.desktopSearchShell.classList.remove("open");
|
||||||
if (clear) {
|
if (clear) {
|
||||||
|
|
@ -146,11 +154,13 @@
|
||||||
|
|
||||||
function navigate(number, replace = false) {
|
function navigate(number, replace = false) {
|
||||||
const hash = `#/page/${encodeURIComponent(number)}`;
|
const hash = `#/page/${encodeURIComponent(number)}`;
|
||||||
if (replace) history.replaceState(null, "", hash);
|
|
||||||
else if (location.hash !== hash) location.hash = hash;
|
|
||||||
closeSearch({ clear: true });
|
closeSearch({ clear: true });
|
||||||
setMobileMode("content");
|
setMobileMode("content");
|
||||||
renderPage(number);
|
if (replace) {
|
||||||
|
history.replaceState(null, "", hash);
|
||||||
|
renderPage(number);
|
||||||
|
} else if (location.hash !== hash) location.hash = hash;
|
||||||
|
else renderPage(number);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildTree(container) {
|
function buildTree(container) {
|
||||||
|
|
@ -342,15 +352,7 @@
|
||||||
button.onclick = () => navigate(page.number);
|
button.onclick = () => navigate(page.number);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderPage(number) {
|
function applyPage(page, index) {
|
||||||
if (!manifest.pages.length) {
|
|
||||||
elements.content.replaceChildren();
|
|
||||||
elements.previous.disabled = elements.next.disabled = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let index = manifest.pages.findIndex((page) => page.number === number);
|
|
||||||
if (index < 0) index = 0;
|
|
||||||
const page = manifest.pages[index];
|
|
||||||
currentNumber = page.number;
|
currentNumber = page.number;
|
||||||
if (routeNumber() !== page.number) history.replaceState(null, "", `#/page/${encodeURIComponent(page.number)}`);
|
if (routeNumber() !== page.number) history.replaceState(null, "", `#/page/${encodeURIComponent(page.number)}`);
|
||||||
document.title = `${page.title} · ${manifest.title}`;
|
document.title = `${page.title} · ${manifest.title}`;
|
||||||
|
|
@ -369,6 +371,53 @@
|
||||||
elements.pageSurface.scrollTo({ top: 0, behavior: "instant" });
|
elements.pageSurface.scrollTo({ top: 0, behavior: "instant" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function renderPage(number) {
|
||||||
|
if (!manifest.pages.length) {
|
||||||
|
elements.content.replaceChildren();
|
||||||
|
elements.previous.disabled = elements.next.disabled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let index = manifest.pages.findIndex((page) => page.number === number);
|
||||||
|
if (index < 0) index = 0;
|
||||||
|
const page = manifest.pages[index];
|
||||||
|
if (page.number === currentNumber && elements.content.childNodes.length) return;
|
||||||
|
|
||||||
|
const currentIndex = manifest.pages.findIndex((candidate) => candidate.number === currentNumber);
|
||||||
|
const direction = currentIndex >= 0 && index < currentIndex ? -1 : 1;
|
||||||
|
const animate = currentIndex >= 0 && !reducedMotion.matches && typeof elements.content.animate === "function";
|
||||||
|
const transitionId = ++pageTransitionId;
|
||||||
|
|
||||||
|
pageAnimation?.cancel();
|
||||||
|
pageAnimation = null;
|
||||||
|
if (animate) {
|
||||||
|
const outgoing = elements.content.animate(
|
||||||
|
[
|
||||||
|
{ opacity: 1, transform: "translateX(0)" },
|
||||||
|
{ opacity: 0, transform: `translateX(${-30 * direction}px)` },
|
||||||
|
],
|
||||||
|
{ duration: 150, easing: "ease-in", fill: "forwards" },
|
||||||
|
);
|
||||||
|
pageAnimation = outgoing;
|
||||||
|
await outgoing.finished.catch(() => {});
|
||||||
|
if (transitionId !== pageTransitionId) return;
|
||||||
|
outgoing.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
applyPage(page, index);
|
||||||
|
if (!animate || transitionId !== pageTransitionId) return;
|
||||||
|
const incoming = elements.content.animate(
|
||||||
|
[
|
||||||
|
{ opacity: 0, transform: `translateX(${30 * direction}px)` },
|
||||||
|
{ opacity: 1, transform: "translateX(0)" },
|
||||||
|
],
|
||||||
|
{ duration: 210, easing: "ease-out" },
|
||||||
|
);
|
||||||
|
pageAnimation = incoming;
|
||||||
|
incoming.finished.catch(() => {}).finally(() => {
|
||||||
|
if (pageAnimation === incoming) pageAnimation = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function plainText(html) {
|
function plainText(html) {
|
||||||
const node = document.createElement("div");
|
const node = document.createElement("div");
|
||||||
node.innerHTML = html;
|
node.innerHTML = html;
|
||||||
|
|
@ -652,6 +701,9 @@
|
||||||
elements.mobileSearchResults.replaceChildren();
|
elements.mobileSearchResults.replaceChildren();
|
||||||
setMobileMode(mobileMode === "menu" ? "content" : "menu");
|
setMobileMode(mobileMode === "menu" ? "content" : "menu");
|
||||||
});
|
});
|
||||||
|
elements.desktopSidebarToggle.addEventListener("click", () => {
|
||||||
|
setDesktopSidebar(document.body.classList.contains("desktop-sidebar-collapsed"));
|
||||||
|
});
|
||||||
document.getElementById("theme-button").addEventListener("click", () => {
|
document.getElementById("theme-button").addEventListener("click", () => {
|
||||||
applyTheme(document.documentElement.dataset.theme === "dark" ? "light" : "dark");
|
applyTheme(document.documentElement.dataset.theme === "dark" ? "light" : "dark");
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,10 @@
|
||||||
<div id="docslib-loading" aria-hidden="true"><div class="docslib-loading-spinner"></div></div>
|
<div id="docslib-loading" aria-hidden="true"><div class="docslib-loading-spinner"></div></div>
|
||||||
<div id="app" class="app-shell">
|
<div id="app" class="app-shell">
|
||||||
<header class="desktop-header">
|
<header class="desktop-header">
|
||||||
<h2 id="desktop-site-title" class="desktop-title">{{DOCSLIB_TITLE}}</h2>
|
<div class="desktop-title-group">
|
||||||
|
<button id="desktop-sidebar-toggle" class="desktop-sidebar-toggle glyph-button" aria-label="☰" aria-expanded="true">☰</button>
|
||||||
|
<h2 id="desktop-site-title" class="desktop-title">{{DOCSLIB_TITLE}}</h2>
|
||||||
|
</div>
|
||||||
<div id="desktop-toolbar" class="desktop-toolbar">
|
<div id="desktop-toolbar" class="desktop-toolbar">
|
||||||
<div id="desktop-search-shell" class="desktop-search-shell">
|
<div id="desktop-search-shell" class="desktop-search-shell">
|
||||||
<div class="search-control">
|
<div class="search-control">
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ def test_all_endpoints_and_self_contained_build(tmp_path):
|
||||||
assert html_response.status_code == 200
|
assert html_response.status_code == 200
|
||||||
assert "http://127.0.0.1:9000/manifest.json" in html
|
assert "http://127.0.0.1:9000/manifest.json" in html
|
||||||
assert "http://127.0.0.1:9000/version" in html
|
assert "http://127.0.0.1:9000/version" in html
|
||||||
assert 'globalThis.COMPILED_FROM_VERSION = "0.3.9";' in html
|
assert 'globalThis.COMPILED_FROM_VERSION = "0.4.0";' in html
|
||||||
assert ".content { --custom-test: yes; }" in html
|
assert ".content { --custom-test: yes; }" in html
|
||||||
assert r'document.body.dataset.customTest = \"yes\";' in html
|
assert r'document.body.dataset.customTest = \"yes\";' in html
|
||||||
assert 'id="docslib-loading"' in html
|
assert 'id="docslib-loading"' in html
|
||||||
|
|
@ -177,6 +177,8 @@ def test_all_endpoints_and_self_contained_build(tmp_path):
|
||||||
assert "--shell: #000000" in html
|
assert "--shell: #000000" in html
|
||||||
assert 'id="desktop-search-input"' in html
|
assert 'id="desktop-search-input"' in html
|
||||||
assert '<header class="desktop-header">' in html
|
assert '<header class="desktop-header">' in html
|
||||||
|
assert 'id="desktop-sidebar-toggle"' in html
|
||||||
|
assert 'aria-label="☰" aria-expanded="true">☰</button>' in html
|
||||||
assert '<aside class="desktop-sidebar">' in html
|
assert '<aside class="desktop-sidebar">' in html
|
||||||
assert 'class="sidebar-header-buttons"' in html
|
assert 'class="sidebar-header-buttons"' in html
|
||||||
assert 'id="mobile-search-input"' in html
|
assert 'id="mobile-search-input"' in html
|
||||||
|
|
@ -203,6 +205,18 @@ def test_all_endpoints_and_self_contained_build(tmp_path):
|
||||||
assert "background: transparent; font-size: 2.3rem" in html
|
assert "background: transparent; font-size: 2.3rem" in html
|
||||||
assert ".mobile-page-tree .tree-link { min-height: 46px; padding: 9px 14px; border-radius: 24px" in html
|
assert ".mobile-page-tree .tree-link { min-height: 46px; padding: 9px 14px; border-radius: 24px" in html
|
||||||
assert ".mobile-menu-button { width: 72px; height: 72px; flex: 0 0 72px; border-radius: 50%; color: var(--text); background: var(--control); font-size: 1.6rem; }" in html
|
assert ".mobile-menu-button { width: 72px; height: 72px; flex: 0 0 72px; border-radius: 50%; color: var(--text); background: var(--control); font-size: 1.6rem; }" in html
|
||||||
|
assert ".desktop-sidebar-toggle { display: grid; width: 44px; height: 44px; flex: 0 0 44px" in html
|
||||||
|
assert "body.desktop-sidebar-collapsed .desktop-sidebar { width: 0; flex-basis: 0;" in html
|
||||||
|
assert 'elements.desktopSidebarToggle.addEventListener("click"' in system_js
|
||||||
|
assert 'document.body.classList.toggle("desktop-sidebar-collapsed", !open)' in system_js
|
||||||
|
assert "localStorage.setItem" not in system_js[system_js.index("function setDesktopSidebar"):system_js.index("function closeSearch")]
|
||||||
|
assert "animation: mobile-menu-enter .26s" in html
|
||||||
|
assert "transform: translateX(-100%)" in html
|
||||||
|
assert "const direction = currentIndex >= 0 && index < currentIndex ? -1 : 1" in system_js
|
||||||
|
assert 'transform: `translateX(${-30 * direction}px)`' in system_js
|
||||||
|
assert 'transform: `translateX(${30 * direction}px)`' in system_js
|
||||||
|
assert 'opacity: 0' in system_js
|
||||||
|
assert "!reducedMotion.matches" in system_js
|
||||||
assert ".nav-card { display: flex; min-height: 48px; align-items: center; gap: 8px; padding: 7px 20px; border: 0; border-radius: 30px" in html
|
assert ".nav-card { display: flex; min-height: 48px; align-items: center; gap: 8px; padding: 7px 20px; border: 0; border-radius: 30px" in html
|
||||||
assert "background: linear-gradient(0deg, var(--shell) 0%, transparent)" in html
|
assert "background: linear-gradient(0deg, var(--shell) 0%, transparent)" in html
|
||||||
assert "<script src=" not in html
|
assert "<script src=" not in html
|
||||||
|
|
@ -218,7 +232,7 @@ def test_all_endpoints_and_self_contained_build(tmp_path):
|
||||||
|
|
||||||
|
|
||||||
def test_build_writes_static_bundle(tmp_path):
|
def test_build_writes_static_bundle(tmp_path):
|
||||||
assert __version__ == "0.3.9"
|
assert __version__ == "0.4.0"
|
||||||
|
|
||||||
@Page("1", "Static page")
|
@Page("1", "Static page")
|
||||||
def static_page():
|
def static_page():
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue