Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/validate-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ jobs:
run: npm run test:kapa-source-groups
- name: Test collapsible TOC groups
run: npm run test:toc-collapsible
- name: Test the active On-this-page entry after an anchor jump
run: npm run test:toc-active-anchor
- name: Test anonymous Ask AI quota
run: npm run test:anon-quota
- name: Test nav positioning leaves the page scroll alone
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,11 @@
"test:nav-lazy-buckets": "node --test tests/nav-lazy-buckets/*.test.js",
"test:has-code-blocks": "node --test tests/has-code-blocks/*.test.js",
"test:docs-page-weight": "node --test tests/docs-page-weight/*.test.js",
"test:all": "npm run test:playground && npm run test:interactive && npm run test:negative-cache && npm run test:head-meta && npm run test:property-tooltips && npm run test:signin-nudge && npm run test:chat-panel-navigation && npm run test:toc-collapsible && npm run test:nav-scroll && npm run test:kapa-source-groups && npm run test:tooltip-touch && npm run test:tooltip-single-open && npm run test:lazy-askai && npm run test:nav-lazy-buckets && npm run test:has-code-blocks && npm run test:docs-page-weight && npm run test:anon-quota",
"test:all": "npm run test:playground && npm run test:interactive && npm run test:negative-cache && npm run test:head-meta && npm run test:property-tooltips && npm run test:signin-nudge && npm run test:chat-panel-navigation && npm run test:toc-collapsible && npm run test:toc-active-anchor && npm run test:nav-scroll && npm run test:kapa-source-groups && npm run test:tooltip-touch && npm run test:tooltip-single-open && npm run test:lazy-askai && npm run test:nav-lazy-buckets && npm run test:has-code-blocks && npm run test:docs-page-weight && npm run test:anon-quota",
"build:wasm": "cd blobl-editor/wasm && GOOS=js GOARCH=wasm go build -o ../../src/static/blobl.wasm .",
"copy:wasm-exec": "cp \"$(go env GOROOT)/lib/wasm/wasm_exec.js\" src/js/vendor/",
"serve:playground": "npx serve ."
"serve:playground": "npx serve .",
"test:toc-active-anchor": "node --test tests/toc-active-anchor/*.test.js"
},
"dependencies": {
"@kapaai/agent-react": "^1.0.1",
Expand Down
3 changes: 3 additions & 0 deletions src/css/doc-bump.css
Original file line number Diff line number Diff line change
Expand Up @@ -777,9 +777,12 @@ html[data-theme=dark] {
margin-top: 0;
}

/* Mirrors the doc.css rule; see the note there for why anywhere and not break-word. */
.aa-DetachedOverlay .doc .tableblock pre,
.aa-DetachedOverlay .doc .tableblock pre code,
.aa-DetachedOverlay .doc .listingblock.wrap pre {
white-space: pre-wrap;
overflow-wrap: anywhere;
}

.aa-DetachedOverlay .doc div.no-wrap pre code {
Expand Down
43 changes: 22 additions & 21 deletions src/css/doc.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
html {
scroll-padding-top: calc(var(--navbar-height) + var(--toolbar-height)); /* 114px */
/* The one place an anchor jump is offset. The "On this page" scroll-spy reads
this same value as its activation line (see src/js/02-on-this-page.js), so
any second offset elsewhere puts a linked heading below the line and
highlights the entry above it. The trailing 16px is the breathing room that
used to live on the headings as scroll-margin-top. */
scroll-padding-top: calc(var(--navbar-height) + var(--toolbar-height) + 16px);
}

.doc {
Expand All @@ -26,7 +31,7 @@ html {

@media screen and (min-width: 1024px) {
html {
scroll-padding-top: calc(var(--navbar-height) + 32px); /* 88px */
scroll-padding-top: calc(var(--navbar-height) + 32px + 24px);
}

.doc {
Expand Down Expand Up @@ -59,24 +64,11 @@ html {
font-weight: var(--heading-font-weight);
}

/* Offset anchor links to account for sticky bar (navbar + component indicator) */
.doc h2[id],
.doc h3[id],
.doc h4[id],
.doc h5[id],
.doc h6[id] {
scroll-margin-top: calc(var(--navbar-height) + 16px);
}

@media screen and (min-width: 1024px) {
.doc h2[id],
.doc h3[id],
.doc h4[id],
.doc h5[id],
.doc h6[id] {
scroll-margin-top: calc(var(--navbar-height) + 24px);
}
}
/* Anchor-link offset lives on html as scroll-padding-top, not here. A
scroll-margin-top on the headings stacked on top of it: the browser parks a
linked heading at the sum of the two, which is below the scroll-spy's
activation line, so clicking an in-page link highlighted the preceding
entry. See tests/toc-active-anchor. */

.doc h1 {
font-size: calc(36 / var(--rem-base) * 1rem);
Expand Down Expand Up @@ -1713,10 +1705,19 @@ details[open] > summary {
display: block;
}

/* NEEDS REVIEW prevent pre in table from causing article to exceed bounds */
/* A code block in a table cell must not set the table's width.
pre-wrap alone was not enough: it wraps between tokens but cannot break
inside one, so a single long identifier still sets the cell's min-content
width and the table outgrows the page. The property reference pages hit this
with `cloud_storage_inventory_hash_path_directory:` at 44 characters, which
is wider than a phone on its own and squeezed the label column to nothing.
overflow-wrap: anywhere is the keyword that lowers min-content, so the token
breaks only when there is genuinely no room; break-word does not. */
.doc .tableblock pre,
.doc .tableblock pre code,
.doc .listingblock.wrap pre {
white-space: pre-wrap;
overflow-wrap: anywhere;
}

.doc pre.highlight code,
Expand Down
112 changes: 65 additions & 47 deletions src/js/02-on-this-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,27 +69,44 @@

if (collapsible) buildCollapsibleGroups(list)

/**
* Make one fragment the active entry, clearing whatever was active before.
*
* The scroll pass can leave several entries active at the bottom of a page,
* so the previous value is either a fragment or an array of them.
*
* @param {string} fragment - The '#id' to activate. Ignored if no entry has it.
*/
function setActive (fragment) {
if (!links[fragment]) return
if (lastActiveFragment && lastActiveFragment !== fragment) {
var previous = Array.isArray(lastActiveFragment) ? lastActiveFragment : [lastActiveFragment]
previous.forEach(function (f) {
if (links[f]) links[f].classList.remove('is-active')
})
}
links[fragment].classList.add('is-active')
revealGroup(links[fragment])
lastActiveFragment = fragment
}

/**
* Hold off the scroll pass for a moment, so it cannot overwrite an activation
* the user just caused. The browser is still settling on the target when a
* click or a hash change lands.
*/
function holdScrollUpdates () {
skipScrollUpdate = true
setTimeout(function () {
skipScrollUpdate = false
}, 100)
}

// Add click handlers to TOC links to immediately highlight clicked item
Object.keys(links).forEach(function (fragment) {
links[fragment].addEventListener('click', function () {
// Immediately update active state on click
if (lastActiveFragment && lastActiveFragment !== fragment) {
if (Array.isArray(lastActiveFragment)) {
lastActiveFragment.forEach(function (f) {
links[f].classList.remove('is-active')
})
} else {
links[lastActiveFragment].classList.remove('is-active')
}
}
links[fragment].classList.add('is-active')
revealGroup(links[fragment])
lastActiveFragment = fragment
// Skip scroll-based updates briefly to prevent flicker during scroll animation
skipScrollUpdate = true
setTimeout(function () {
skipScrollUpdate = false
}, 100)
setActive(fragment)
holdScrollUpdates()
})
})

Expand Down Expand Up @@ -144,22 +161,8 @@
// Update sidebar TOC active state immediately
var fragment = link.getAttribute('href')
if (fragment && links[fragment]) {
if (lastActiveFragment && lastActiveFragment !== fragment) {
if (Array.isArray(lastActiveFragment)) {
lastActiveFragment.forEach(function (f) {
links[f].classList.remove('is-active')
})
} else {
links[lastActiveFragment].classList.remove('is-active')
}
}
links[fragment].classList.add('is-active')
revealGroup(links[fragment])
lastActiveFragment = fragment
skipScrollUpdate = true
setTimeout(function () {
skipScrollUpdate = false
}, 100)
setActive(fragment)
holdScrollUpdates()
}
})
})
Expand All @@ -170,10 +173,12 @@

window.addEventListener('load', function () {
onScroll()
revealHashTarget()
syncToHashTarget()
window.addEventListener('scroll', onScroll, { passive: true })
// In-page links and back/forward change the hash without a load, so reveal on those too
window.addEventListener('hashchange', revealHashTarget)
// In-page links and back/forward change the hash without a load, so the
// highlight has to follow on those too. onScroll alone cannot: it runs on
// scroll, and a hash change does not always produce one.
window.addEventListener('hashchange', syncToHashTarget)
// On initial load, scroll active item into view (e.g., when navigating to a hash)
scrollActiveIntoView()
})
Expand Down Expand Up @@ -323,24 +328,37 @@
}

/**
* Open the group that holds the entry the URL hash points at, on load and on every hash change.
* Browsers park a linked heading at scroll-padding-top + scroll-margin-top, below the activation
* line onScroll uses, so the heading above it becomes active instead. For the first entry of a
* group that heading belongs to the previous group, which would leave the target's own group
* collapsed.
* Make the entry the URL hash names the active one, on load and on every hash
* change, and open the group holding it.
*
* The hash is authoritative here rather than the scroll position, because the
* two disagree. A browser parks a linked heading at scroll-padding-top plus
* any scroll-margin-top on the heading, and onScroll's activation line is
* scroll-padding-top alone; whenever the heading lands below that line the
* scroll pass picks the heading above it and highlights the wrong entry. The
* stylesheet now offsets once so the two line up, and this keeps the highlight
* correct even if that ever drifts again.
*
* It also covers the case onScroll cannot see at all: an in-page link fires
* hashchange, and a hash change does not always move the scroll position
* enough to produce a scroll event.
*/
function revealHashTarget () {
function syncToHashTarget () {
var hash = window.location.hash
if (!hash) return
var link = links[hash]
if (!link && ~hash.indexOf('%')) {
var fragment = links[hash] ? hash : null
if (!fragment && ~hash.indexOf('%')) {
try {
link = links[decodeURIComponent(hash)]
var decoded = decodeURIComponent(hash)
if (links[decoded]) fragment = decoded
} catch (e) {
return
}
}
if (link) revealGroup(link)
if (!fragment) return
setActive(fragment)
holdScrollUpdates()
scrollActiveIntoView()
}

function find (selector, from) {
Expand Down
Loading
Loading