mirror of
https://github.com/kbenestad/mdcms.git
synced 2026-06-18 15:24:32 +00:00
Enable Back button and fix clean-URL reloads
Renderer (app/index.html): - navigateTo now pushes a history entry for user navigations (pushState), while the initial load, back/forward (popstate/hashchange), and category re-renders still replaceState. The browser Back button now returns to the previous page instead of leaving the site. Service worker (mdcms.py generator + app/service-worker.js): - Serve the cached index.html app shell for navigation requests. Reloading a clean URL like /section-id previously 404'd on the static host before any JavaScript ran; the shell fallback lets the client-side router resolve the path. Also makes pretty-URL reloads work offline. https://claude.ai/code/session_018KXUwmSNMGF2UBywTChCcS
This commit is contained in:
parent
df0f179004
commit
a5127727f0
4 changed files with 44 additions and 10 deletions
|
|
@ -1276,7 +1276,7 @@ body {
|
|||
window.history.replaceState(null, '', url);
|
||||
maybeLoadCategoryFont(code).then(() => {
|
||||
renderNav();
|
||||
if (currentPage) navigateTo(currentPage);
|
||||
if (currentPage) navigateTo(currentPage, { replace: true });
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -3120,7 +3120,12 @@ function fmtDatetime(dtStr) {
|
|||
}
|
||||
|
||||
// ─── Page loading ─────────────────────────────────────────
|
||||
async function navigateTo(file) {
|
||||
// opts.replace: replace the current history entry instead of pushing a new
|
||||
// one. Used for the initial load and for back/forward (popstate/hashchange),
|
||||
// where pushing would corrupt the history stack. User navigations push, so
|
||||
// the browser Back button returns to the previous page.
|
||||
async function navigateTo(file, opts) {
|
||||
const replace = !!(opts && opts.replace);
|
||||
const contentEl = document.getElementById('pageContent');
|
||||
|
||||
// Guard the router: only fetch relative .md paths. This blocks loading
|
||||
|
|
@ -3169,7 +3174,7 @@ function fmtDatetime(dtStr) {
|
|||
u.pathname = basePath;
|
||||
u.hash = '#' + file;
|
||||
}
|
||||
window.history.replaceState(null, '', u);
|
||||
window.history[replace ? 'replaceState' : 'pushState'](null, '', u);
|
||||
|
||||
contentEl.innerHTML = '<div class="loading-spinner"></div>';
|
||||
|
||||
|
|
@ -3243,14 +3248,14 @@ function fmtDatetime(dtStr) {
|
|||
const page = getPageFromHash();
|
||||
// Ignore in-page heading anchors (e.g. #installation) — only route real .md
|
||||
// pages. Without this, clicking a heading link wipes the page with a 404.
|
||||
if (page && page !== currentPage && isSafePagePath(page)) navigateTo(page);
|
||||
if (page && page !== currentPage && isSafePagePath(page)) navigateTo(page, { replace: true });
|
||||
});
|
||||
|
||||
window.addEventListener('popstate', () => {
|
||||
const slug = window.location.pathname.replace(basePath, '').replace(/^\//, '').replace(/\/$/, '');
|
||||
const pathPage = slug ? resolveSlugToFile(slug) : null;
|
||||
const page = pathPage || getPageFromHash();
|
||||
if (page && page !== currentPage && isSafePagePath(page)) navigateTo(page);
|
||||
if (page && page !== currentPage && isSafePagePath(page)) navigateTo(page, { replace: true });
|
||||
});
|
||||
|
||||
// ─── Scroll to top ───────────────────────────────────────
|
||||
|
|
@ -3352,7 +3357,7 @@ function fmtDatetime(dtStr) {
|
|||
}
|
||||
|
||||
const hashPage = getPageFromHash();
|
||||
await navigateTo(routeFromPath || hashPage || defaultPage());
|
||||
await navigateTo(routeFromPath || hashPage || defaultPage(), { replace: true });
|
||||
} catch (err) {
|
||||
document.getElementById('app').innerHTML = `<div style="max-width:600px;margin:4rem auto;padding:2rem;text-align:center;font-family:system-ui;">
|
||||
<h1 style="color:#EF4444;font-size:1.5rem;">MD-CMS Error</h1>
|
||||
|
|
|
|||
|
|
@ -59,8 +59,18 @@ self.addEventListener('activate', event => {
|
|||
});
|
||||
|
||||
self.addEventListener('fetch', event => {
|
||||
if (event.request.method !== 'GET') return;
|
||||
const req = event.request;
|
||||
if (req.method !== 'GET') return;
|
||||
// App-shell routing: serve cached index.html for every navigation, including
|
||||
// clean URLs like /section-id on reload. Without this the static host returns
|
||||
// 404 for those paths before any JavaScript runs. Works offline too.
|
||||
if (req.mode === 'navigate') {
|
||||
event.respondWith(
|
||||
caches.match('index.html').then(shell => shell || fetch(req))
|
||||
);
|
||||
return;
|
||||
}
|
||||
event.respondWith(
|
||||
caches.match(event.request).then(cached => cached || fetch(event.request))
|
||||
caches.match(req).then(cached => cached || fetch(req))
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,6 +24,15 @@ Changes merged into `development` that have not yet been released to `main`.
|
|||
- **Nested `mdcms` tags now hydrate.** Tags emitted inside a tab, accordion, or
|
||||
callout body (e.g. a post list inside a tab) are processed by re-sweeping
|
||||
until none remain, instead of rendering as empty divs.
|
||||
- **Browser Back/Forward now navigate within the app.** User navigations use
|
||||
`history.pushState` (the initial load, back/forward, and category re-renders
|
||||
still replace), so Back returns to the previous page instead of leaving the
|
||||
site.
|
||||
- **Clean-URL reloads (Ctrl-R) work.** The generated service worker now serves
|
||||
the cached `index.html` app shell for any navigation request, so reloading a
|
||||
pretty URL such as `example.com/section-id` no longer 404s before the
|
||||
client-side router runs. This also makes pretty-URL reloads work offline.
|
||||
(Generated by `mdcms build`; `app/service-worker.js` regenerated.)
|
||||
- **`marked` is configured once** instead of re-registering the renderer on
|
||||
every page render.
|
||||
- Stored `md-cms-theme` value is validated against `light`/`dark` before use;
|
||||
|
|
|
|||
14
mdcms.py
14
mdcms.py
|
|
@ -663,9 +663,19 @@ self.addEventListener('activate', event => {{
|
|||
}});
|
||||
|
||||
self.addEventListener('fetch', event => {{
|
||||
if (event.request.method !== 'GET') return;
|
||||
const req = event.request;
|
||||
if (req.method !== 'GET') return;
|
||||
// App-shell routing: serve cached index.html for every navigation, including
|
||||
// clean URLs like /section-id on reload. Without this the static host returns
|
||||
// 404 for those paths before any JavaScript runs. Works offline too.
|
||||
if (req.mode === 'navigate') {{
|
||||
event.respondWith(
|
||||
caches.match('index.html').then(shell => shell || fetch(req))
|
||||
);
|
||||
return;
|
||||
}}
|
||||
event.respondWith(
|
||||
caches.match(event.request).then(cached => cached || fetch(event.request))
|
||||
caches.match(req).then(cached => cached || fetch(req))
|
||||
);
|
||||
}});
|
||||
"""
|
||||
|
|
|
|||
Loading…
Reference in a new issue