PageSourceSearch

https://lavinmq.com/js/docs.js

js lavinmq.com collected 2026-09-24 18:45:36 UTC 6,503 bytes, 209 lines download raw bytes

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.