| /* | |
| * Licensed to the Apache Software Foundation (ASF) under one | |
| * or more contributor license agreements. See the NOTICE file | |
| * distributed with this work for additional information | |
| * regarding copyright ownership. The ASF licenses this file | |
| * to you under the Apache License, Version 2.0 (the | |
| * "License"); you may not use this file except in compliance | |
| * with the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, | |
| * software distributed under the License is distributed on an | |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| * KIND, either express or implied. See the License for the | |
| * specific language governing permissions and limitations | |
| * under the License. | |
| */ | |
| /* | |
| * Comet docs UX enhancements layered on top of pydata_sphinx_theme: | |
| * - "Home" tab injected at the start of the top navbar (toctree can't | |
| * self-reference, so the landing page link lives here) | |
| * - Click-to-toggle expand/collapse arrows on sidebar nav groups | |
| * - In-sidebar search filter with character highlighting (replaces the | |
| * default Sphinx redirect-to-/search.html flow) | |
| * - Verbose "Comet X.Y.Z User Guide" breadcrumb labels trimmed to "X.Y.Z" | |
| * - Right-side TOC auto-hidden on pages with too few headings to navigate | |
| * | |
| * All behaviour is additive — pydata's existing keyboard navigation, theme | |
| * switcher, search index, and toctree generation continue to work unchanged. | |
| */ | |
| (function () { | |
| 'use strict'; | |
| // ── Sidebar collapse/expand toggle buttons ───────────────────────── | |
| // Pydata's toctree renders a flat <ul> tree with no toggles. We inject a | |
| // real <button> for every nav item that has children so the user can | |
| // expand/collapse sections independently of which page they're on. | |
| function initSidebarToggle() { | |
| // Match any toctree item with children, not just L1/L2 — Older Versions | |
| // contains nested version groups (0.15.x, 0.14.x) at L3 that need their | |
| // own expand/collapse arrows. | |
| var items = document.querySelectorAll('.bd-links li[class*="toctree-l"]'); | |
| items.forEach(function (li) { | |
| var ul = li.querySelector(':scope > ul'); | |
| var link = li.querySelector(':scope > a'); | |
| if (!ul || !link) return; | |
| if (li.querySelector(':scope > .comet-toggle')) return; | |
| // Auto-expand the section that contains the current page so the | |
| // active page is always visible without an extra click. | |
| if (li.classList.contains('current')) { | |
| li.classList.add('comet-expanded'); | |
| } | |
| var btn = document.createElement('button'); | |
| btn.type = 'button'; | |
| btn.className = 'comet-toggle'; | |
| btn.setAttribute('aria-label', 'Toggle section'); | |
| btn.setAttribute( | |
| 'aria-expanded', | |
| li.classList.contains('comet-expanded') ? 'true' : 'false' | |
| ); | |
| btn.addEventListener('click', function (e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| var expanded = li.classList.toggle('comet-expanded'); | |
| btn.setAttribute('aria-expanded', expanded ? 'true' : 'false'); | |
| }); | |
| link.parentNode.insertBefore(btn, link.nextSibling); | |
| }); | |
| } | |
| // ── In-sidebar search filter ──────────────────────────────────────── | |
| // Replaces Sphinx's "submit form → land on /search.html" flow with a | |
| // live filter that hides non-matching nav items and highlights the | |
| // matched characters in-place. Search of full document content still | |
| // works via the regular Sphinx search page (pressing Enter is just | |
| // suppressed here so the form doesn't navigate away). | |
| function escapeRegex(s) { | |
| return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | |
| } | |
| function escapeHtml(s) { | |
| var map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; | |
| return s.replace(/[&<>"']/g, function (c) { return map[c]; }); | |
| } | |
| function initSidebarSearch() { | |
| var input = document.querySelector( | |
| '.bd-sidebar form.bd-search input[type="search"], ' + | |
| '.bd-sidebar form.bd-search input.form-control' | |
| ); | |
| var form = document.querySelector('.bd-sidebar form.bd-search'); | |
| var nav = document.querySelector('.bd-links'); | |
| if (!input || !nav) return; | |
| if (form) { | |
| form.addEventListener('submit', function (e) { e.preventDefault(); }); | |
| } | |
| // Cache the original text so we can restore it (and re-highlight on | |
| // every keystroke) without losing nav item labels. | |
| var allLinks = Array.from(nav.querySelectorAll('a.reference')); | |
| allLinks.forEach(function (a) { | |
| if (!a.dataset.cometOrigText) { | |
| a.dataset.cometOrigText = a.textContent; | |
| } | |
| }); | |
| var allListItems = Array.from(nav.querySelectorAll('li')); | |
| function applyFilter(query) { | |
| query = (query || '').trim(); | |
| if (!query) { | |
| // Clear filter: restore original text, drop filter classes. | |
| allLinks.forEach(function (a) { | |
| a.innerHTML = escapeHtml(a.dataset.cometOrigText); | |
| }); | |
| allListItems.forEach(function (li) { | |
| li.classList.remove( | |
| 'comet-filter-hidden', | |
| 'comet-filter-match', | |
| 'comet-filter-expanded' | |
| ); | |
| }); | |
| nav.classList.remove('comet-filtering'); | |
| return; | |
| } | |
| nav.classList.add('comet-filtering'); | |
| var re = new RegExp('(' + escapeRegex(query) + ')', 'ig'); | |
| allListItems.forEach(function (li) { | |
| li.classList.add('comet-filter-hidden'); | |
| li.classList.remove('comet-filter-match', 'comet-filter-expanded'); | |
| }); | |
| allLinks.forEach(function (a) { | |
| var orig = a.dataset.cometOrigText; | |
| if (re.test(orig)) { | |
| re.lastIndex = 0; | |
| a.innerHTML = escapeHtml(orig).replace( | |
| re, | |
| '<mark class="comet-hl">$1</mark>' | |
| ); | |
| // Reveal this item and every ancestor li so the match is reachable. | |
| var li = a.closest('li'); | |
| while (li && nav.contains(li)) { | |
| li.classList.remove('comet-filter-hidden'); | |
| li.classList.add('comet-filter-match', 'comet-filter-expanded'); | |
| li = li.parentElement.closest('li'); | |
| } | |
| } else { | |
| a.innerHTML = escapeHtml(orig); | |
| } | |
| }); | |
| } | |
| var debounceTimer; | |
| input.addEventListener('input', function () { | |
| clearTimeout(debounceTimer); | |
| var q = input.value; | |
| debounceTimer = setTimeout(function () { applyFilter(q); }, 80); | |
| }); | |
| input.addEventListener('keydown', function (e) { | |
| if (e.key === 'Escape') { | |
| input.value = ''; | |
| applyFilter(''); | |
| } else if (e.key === 'Enter') { | |
| e.preventDefault(); | |
| } | |
| }); | |
| } | |
| // ── Inject "Home" link into the top navbar ───────────────────────── | |
| // The toctree can't self-reference, so we add the Home tab via JS. | |
| // It's prepended to the desktop nav AND the mobile drawer. The link | |
| // is computed from the navbar logo's href so it stays correct at any | |
| // page depth. | |
| function initHomeNavLink() { | |
| var logo = document.querySelector('a.navbar-brand'); | |
| var homeHref = logo ? logo.getAttribute('href') : null; | |
| if (!homeHref) return; | |
| var isHomePage = !!document.querySelector('.comet-hero'); | |
| document | |
| .querySelectorAll('.bd-navbar-elements.navbar-nav') | |
| .forEach(function (nav) { | |
| if (nav.querySelector('.comet-home-nav-item')) return; | |
| var li = document.createElement('li'); | |
| li.className = 'nav-item comet-home-nav-item'; | |
| if (isHomePage) li.classList.add('active', 'current'); | |
| var a = document.createElement('a'); | |
| a.className = 'nav-link nav-internal'; | |
| a.setAttribute('href', homeHref); | |
| if (isHomePage) a.setAttribute('aria-current', 'page'); | |
| a.textContent = 'Home'; | |
| li.appendChild(a); | |
| nav.insertBefore(li, nav.firstChild); | |
| }); | |
| } | |
| // ── Section titles toggle collapse/expand too ────────────────────── | |
| // pydata's toctree hoists the link out of <details> for nav items that | |
| // are both a page and a section header (e.g. "0.17.x (current)", | |
| // "Expression Compatibility"), so by default only the chevron toggles; | |
| // clicking the title just navigates. Since navigating to a page inside | |
| // its own section makes that section "current" (auto-expanded), a title | |
| // click reads as "expand-only" — clicking it again just reloads the same | |
| // page, which is still expanded. Make the title toggle the same | |
| // <details> the chevron does, so both controls behave symmetrically. | |
| function initSectionTitleToggle() { | |
| var titles = document.querySelectorAll('.bd-links li.has-children > a.reference'); | |
| titles.forEach(function (link) { | |
| var details = link.nextElementSibling; | |
| if (!details || details.tagName !== 'DETAILS') return; | |
| link.setAttribute('role', 'button'); | |
| link.setAttribute('aria-expanded', details.open ? 'true' : 'false'); | |
| // Listen on <details>'s own toggle event, not just this link's click, | |
| // so aria-expanded stays correct whether the chevron/summary or the | |
| // title triggered the change. | |
| details.addEventListener('toggle', function () { | |
| link.setAttribute('aria-expanded', details.open ? 'true' : 'false'); | |
| }); | |
| link.addEventListener('click', function (e) { | |
| e.preventDefault(); | |
| details.open = !details.open; | |
| }); | |
| }); | |
| } | |
| // ── Breadcrumb trim ──────────────────────────────────────────────── | |
| // "Comet 0.16.0-SNAPSHOT User Guide" reads as noise in a breadcrumb | |
| // when the section is already shown in the top nav. Trim to "0.16.0- | |
| // SNAPSHOT" and let the parent breadcrumb item carry the section name. | |
| function initBreadcrumbTrim() { | |
| document | |
| .querySelectorAll('.bd-breadcrumbs .breadcrumb-item a') | |
| .forEach(function (a) { | |
| var match = a.textContent | |
| .trim() | |
| .match(/^Comet\s+([\d.x\-A-Za-z]+)\s+User Guide$/); | |
| if (match) a.textContent = match[1]; | |
| }); | |
| } | |
| // ── Right-side TOC visibility ────────────────────────────────────── | |
| // Reference-style pages (config tables, expression lists) and short | |
| // narrative pages don't benefit from an in-page TOC. Hide the rail | |
| // when there are fewer than 3 anchors so it doesn't eat real estate | |
| // for nothing. | |
| function initSecondaryTocVisibility() { | |
| var sidebar = document.querySelector('.bd-sidebar-secondary'); | |
| if (!sidebar) return; | |
| var anchors = sidebar.querySelectorAll( | |
| 'nav.bd-toc a.reference, .page-toc a.reference' | |
| ); | |
| if (anchors.length < 3) { | |
| sidebar.classList.add('comet-toc-empty'); | |
| } | |
| } | |
| function bootstrap() { | |
| initHomeNavLink(); | |
| initSidebarToggle(); | |
| initSectionTitleToggle(); | |
| initSidebarSearch(); | |
| initSecondaryTocVisibility(); | |
| initBreadcrumbTrim(); | |
| } | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', bootstrap); | |
| } else { | |
| bootstrap(); | |
| } | |
| })(); |