diff --git a/public/js/admin-docs.js b/public/js/admin-docs.js index 2062fda..4e90110 100644 --- a/public/js/admin-docs.js +++ b/public/js/admin-docs.js @@ -107,6 +107,43 @@ }).join(''); } + // marked no longer guarantees heading ids across versions, and the docs + // reader is an internal scroll container. Add stable GitHub-style ids and + // handle same-page #toc links by scrolling the reader, not the window. + function slugHeading(text) { + return String(text || '') + .trim() + .toLowerCase() + .replace(/\s/g, '-') + .replace(/[^a-z0-9_-]/g, ''); + } + + function prepareDocAnchors(body) { + if (!body) return; + var seen = {}; + body.querySelectorAll('h1,h2,h3,h4,h5,h6').forEach(function (h) { + var base = h.id || slugHeading(h.textContent || ''); + if (!base) return; + var id = base; + var n = seen[base] || 0; + if (n) id = base + '-' + n; + seen[base] = n + 1; + h.id = id; + }); + } + + function scrollReaderToHash(hash) { + var body = $('docs-reader-body'); + var reader = $('docs-reader'); + if (!body || !reader || !hash) return false; + var id = String(hash).replace(/^#/, ''); + if (!id) return false; + var target = document.getElementById(id); + if (!target || !body.contains(target)) return false; + reader.scrollTop = target.offsetTop - body.offsetTop - 8; + return true; + } + function loadTree() { if (_treeLoaded) return; fetch('/api/admin/docs/tree', { headers: getAuthHeaders() }) @@ -152,6 +189,7 @@ return; } body.innerHTML = data.html || ''; + prepareDocAnchors(body); if (meta) { meta.textContent = relPath + ' • ' + (data.bytes != null ? (data.bytes + ' bytes') : ''); } @@ -160,6 +198,7 @@ // Scroll content area to top so deep-link readers don't land mid-doc var reader = $('docs-reader'); if (reader) reader.scrollTop = 0; + if (window.location.hash) setTimeout(function () { scrollReaderToHash(window.location.hash); }, 0); }) .catch(function (err) { body.innerHTML = '
' + escHtml(err.message || String(err)) + '
'; @@ -203,6 +242,15 @@ arrow.classList.toggle('fa-chevron-right', !willOpen); } persistExpanded(); + return; + } + var docLink = e.target.closest('#docs-reader-body a[href^="#"]'); + if (docLink) { + var href = docLink.getAttribute('href'); + if (href && scrollReaderToHash(href)) { + e.preventDefault(); + try { history.replaceState(null, '', href); } catch (_) { window.location.hash = href; } + } } });