1// Documentation page functionality: sidebar scroll, accordion, TOC, and feedback 2window.addEventListener("DOMContentLoaded", function () { 3 const sidebar = document.getElementById("documentation-sidebar"); 4 const activeLink = sidebar.querySelector(".active"); 5 6 if (activeLink) { 7 const sidebarRect = sidebar.getBoundingClientRect(); 8 const linkRect = activeLink.getBoundingClientRect(); 9 10 const offset = 11 linkRect.top - 12 sidebarRect.top - 13 sidebar.clientHeight / 2 + 14 linkRect.height / 2; 15 sidebar.scrollTop += offset; 16 } 17}); 18 19// Collapsible sidebar toggle 20(function () { 21 const root = document.documentElement; 22 const collapseBtn = document.getElementById("sidebar-collapse-btn"); 23 const expandBtn = document.getElementById("sidebar-expand-btn"); 24 if (!collapseBtn || !expandBtn) return; 25 26 function syncAria() { 27 const collapsed = root.dataset.sidebarCollapsed === "true"; 28 collapseBtn.setAttribute("aria-expanded", String(!collapsed)); 29 expandBtn.setAttribute("aria-expanded", String(!collapsed)); 30 } 31 32 function setCollapsed(collapsed) { 33 if (collapsed) { 34 root.dataset.sidebarCollapsed = "true"; 35 } else { 36 delete root.dataset.sidebarCollapsed; 37 } 38 try { 39 localStorage.setItem("docs-sidebar-collapsed", String(collapsed)); 40 } catch (e) {} 41 syncAria(); 42 } 43 44 syncAria(); 45 collapseBtn.addEventListener("click", () => setCollapsed(true)); 46 expandBtn.addEventListener("click", () => setCollapsed(false)); 47})(); 48 49document.querySelectorAll(".accordion-header").forEach((button) => { 50 button.addEventListener("click", () => { 51 const expanded = button.getAttribute("aria-expanded") === "true"; 52 const content = document.getElementById( 53 button.getAttribute("aria-controls"), 54 ); 55 56 // Open if not open 57 if (!expanded) { 58 button.setAttribute("aria-expanded", "true"); 59 content.classList.add("open"); 60 content.style.maxHeight = content.scrollHeight + 40 + "px"; 61 } else { 62 button.setAttribute("aria-expanded", "false"); 63 content.classList.remove("open"); 64 content.style.maxHeight = null; 65 } 66 }); 67}); 68 69/* Anchor links */ 70const tocList = document.getElementById("toc-list"); 71const headings = document.querySelectorAll("h1[id], h2[id], h3[id]"); 72const toc = document.querySelector(".toc"); 73 74if (headings.length === 0 && tocList) { 75 toc.style.display = "none"; 76} 77 78// Build TOC
79headings.forEach((h) => { 80 const li = document.createElement("li"); 81 const a = document.createElement("a"); 82 a.href = "#" + h.id; 83 a.textContent = h.textContent; 84 li.appendChild(a); 85 86 // indent deeper headings 87 if (h.tagName === "H2") li.style.marginLeft = "0"; 88 if (h.tagName === "H3") li.style.marginLeft = "0"; 89 90 tocList.appendChild(li); 91}); 92 93// Scroll spy: highlight current section 94const links = tocList.querySelectorAll("a"); 95 96const observer = new IntersectionObserver( 97 (entries) => { 98 entries.forEach((entry) => { 99 if (entry.isIntersecting) { 100 links.forEach((link) => link.classList.remove("active")); 101 const activeLink = tocList.querySelector( 102 `a[href="#${entry.target.id}"]`, 103 ); 104 if (activeLink) activeLink.classList.add("active"); 105 } 106 }); 107 }, 108 { 109 rootMargin: "-20% 0px -35% 0px", // adjust "active zone" 110 threshold: 0, 111 }, 112); 113 114headings.forEach((h) => observer.observe(h)); 115 116// Fix: update active class on click too 117links.forEach((link) => { 118 link.addEventListener("click", (e) => { 119 links.forEach((l) => l.classList.remove("active")); 120 link.classList.add("active"); 121 }); 122}); 123 124/* Was this helpful? + Plausible tracking */ 125(function () { 126 const container = document.getElementById("helpful-feedback"); 127 if (!container) return; 128 129 // Safe tracker (queues if plausible hasn't loaded yet) 130 window.plausible = 131 window.plausible || 132 function () { 133 (window.plausible.q = window.plausible.q || []).push(arguments); 134 }; 135 function track(eventName, props) { 136 try { 137 window.plausible(eventName, { props }); 138 } catch (e) {} 139 } 140 141 function renderThanks() { 142 container.innerHTML = ` 143 <div class="flex items-center gap-1 rounded-2xl bg-green-800/10 px-2 py-1 text-sm text-[#fafafa]"> 144 <svg class="h-5 w-5 text-green-800" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> 145 <path fill-rule="evenodd" d="M16.707 5.293a1 1 0 0 1 0 1.414l-7.5 7.5a1 1 0 0 1-1.414 0l-3.5-3.5a1 1 0 1 1 1.414-1.414L8.5 11.586l6.793-6.793a1 1 0 0 1 1.414 0Z" clip-rule="evenodd" /> 146 </svg> 147 <span class="font-inter">Thanks for your feedback!</span> 148 </div> 149 `; 150 } 151 152 function renderFollowUp() { 153 container.innerHTML = ` 154 <div class="font-inter text-sm text-[#fafafa]"> 155 Mind telling us how we can improve? 156 <span class="inline-flex items-center gap-2 sm:ml-2"> 157 <input id="docs-feedback-input" type="text" maxlength="300" 158 placeholder="Your feedback (optional)" 159 class="w-56 rounded-md border border-[#414040] text-sm bg-[#2f2f2f] px-2 py-1 text-[#FAFAFA] placeholder:text-[#A0A0A0] focus:outline-none focus:border-green-800 focus:ring-0" /> 160 <button id="docs-feedback-send" type="button" 161 class="rounded-md bg-green-800 px-2 py-1 font-inter text-sm font-semibold text-[#0D0D0D] transition hover:bg-green-700">Send</button> 162 </span> 163 </div> 164 `; 165 166 const input = document.getElementById("docs-feedback-input"); 167 const btn = document.getElementById("docs-feedback-send"); 168 169 function submitFeedback() { 170 const text = (input.value || "").trim().slice(0, 300); 171 if (text) { 172 track("Docs Feedback", { 173 feedback: text, 174 page_title: document.title, 175 }); 176 } 177 renderThanks(); 178 } 179 180 btn.addEventListener("click", submitFeedback, { once: true }); 181 input.addEventListener("keydown", (e) => { 182 if (e.key === "Enter") { 183 e.preventDefault(); 184 submitFeedback(); 185 } 186 }); 187 // focus input 188 setTimeout(() => input?.focus(), 0); 189 } 190 191 const yesNoHandler = (ev) => { 192 const type = ev.target.getAttribute("data-feedback"); 193 if (!type) return; 194 195 // Track the Yes/No click immediately 196 track("Docs Helpful", { 197 outcome: type, // "yes" | "no" 198 page_title: document.title, // optional extra context 199 }); 200 201 if (type === "yes") { 202 renderThanks(); 203 } else { 204 renderFollowUp(); 205 } 206 }; 207 208 container.addEventListener("click", yesNoHandler); 209})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.