PageSourceSearch

https://getnametag.com/js/docs-redesign.js

js getnametag.com collected 2026-09-24 18:34:33 UTC 14,560 bytes, 398 lines download raw bytes

1/**
2 * Copyright 2026 Nametag Inc.
3 *
4 * All information contained herein is the property of Nametag Inc.. The
5 * intellectual and technical concepts contained herein are proprietary, trade
6 * secrets, and/or confidential to Nametag, Inc. and may be covered by U.S.
7 * and Foreign Patents, patents in process, and are protected by trade secret or
8 * copyright law. Reproduction or distribution, in whole or in part, is
9 * forbidden except by express written permission of Nametag, Inc.
10 */
11
12/* 🤖 ross — front-end behavior for the redesigned docs:
13 *   - ⌘K command palette over the topic index
14 *   - smooth-scroll + scroll-spy for the "on this page" TOC (and the legacy
15 *     left-rail TableOfContents used by kb / legal / techblog)
16 *   - code-block copy chrome
17 *   - callout icons on blockquotes
18 *   - mobile sidebar drawer
19 */
20(function () {
21  "use strict";
22
23  var HEADER_OFFSET = 84;
24
25  /* 🤖 Shared focus helpers for the palette + mobile popover. */
26  var FOCUSABLE =
27    'a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
28  function focusable(container) {
29    return Array.prototype.filter.call(container.querySelectorAll(FOCUSABLE), function (el) {
30      return el.offsetWidth > 0 || el.offsetHeight > 0 || el === document.activeElement;
31    });
32  }
33  function trapTab(container, e) {
34    if (e.key !== "Tab") return;
35    var items = focusable(container);
36    if (!items.length) return;
37    var first = items[0];
38    var last = items[items.length - 1];
39    if (e.shiftKey && document.activeElement === first) {
40      e.preventDefault();
41      last.focus();
42    } else if (!e.shiftKey && document.activeElement === last) {
43      e.preventDefault();
44      first.focus();
45    }
46  }
47
48  /* ---------------- Command palette ---------------- */
49  function initPalette() {
50    var palette = document.getElementById("nt-palette");
51    if (!palette) return;
52
53    var index = window.ntSearchIndex || [];
54
55    var input = palette.querySelector(".nt-palette__input");
56    var resultsEl = palette.querySelector(".nt-palette__results");
57    var countEl = palette.querySelector(".nt-palette__count");
58    var active = -1;
59    var current = [];
60
61    function render() {
62      var q = input.value.trim().toLowerCase();
63      var matched = q
64        ? index.filter(function (t) {
65            return (t.title + " " + t.desc + " " + t.group).toLowerCase().indexOf(q) !== -1;
66          })
67        : index.slice(0, 7);
68      current = matched.slice(0, 8);
69      active = current.length ? 0 : -1;
70
71      resultsEl.innerHTML = "";
72      if (q && current.length === 0) {
73        var empty = document.createElement("div");
74        empty.className = "nt-palette__empty";
75        empty.textContent = 'No results for "' + input.value + '".';
76        resultsEl.appendChild(empty);
77      } else {
78        current.forEach(function (t, i) {
79          var a = document.createElement("a");
80          a.className = "nt-palette__result" + (i === active ? " is-active" : "");
81          a.href = t.href;
82          a.innerHTML =
83            '<span class="ms">' +
84            escapeHtml(t.icon || "description") +
85            "</span>" +
86            '<span class="nt-palette__result-text">' +
87            '<span class="nt-palette__result-title"></span>' +
88            '<span class="nt-palette__result-sub"></span>' +
89            "</span>" +
90            '<span class="ms nt-palette__result-arrow">arrow_forward</span>';
91          a.querySelector(".nt-palette__result-title").textContent = t.title;
92          a.querySelector(".nt-palette__result-sub").textContent = t.group + " · " + t.desc;
93          a.addEventListener("mousemove", function () {
94            setActive(i);
95          });
96          resultsEl.appendChild(a);
97        });
98      }
99      if (countEl) countEl.textContent = matched.length + " result" + (matched.length === 1 ? "" : "s");
100    }
101
102    function setActive(i) {
103      active = i;
104      var rows = resultsEl.querySelectorAll(".nt-palette__result");
105      rows.forEach(function (r, idx) {
106        r.classList.toggle("is-active", idx === active);
107      });
108    }
109
110    var lastTrigger = null;
111    var suppressAutoOpen = false; // 🤖 stop focus-return from re-opening the palette
112
113    function open(trigger) {
114      if (trigger) lastTrigger = trigger;
115      palette.hidden = false;
116      input.value = "";
117      render();
118      setTimeout(function () {
119        try {
120          input.focus();
121        } catch (e) {}
122      }, 30);
123    }
124    function close() {
125      if (palette.hidden) return;
126      palette.hidden = true;
127      // 🤖 return focus to whatever opened the palette (a11y), without re-triggering
128      // the focus-to-open behavior on the search buttons.
129      if (lastTrigger) {
130        suppressAutoOpen = true;
131        try {
132          lastTrigger.focus();
133        } catch (e) {}
134        setTimeout(function () {
135          suppressAutoOpen = false;
136        }, 120);
137      }
138    }
139    function isOpen() {
140      return !palette.hidden;
141    }
142
143    input.addEventListener("input", render);
144
145    // 🤖 focus trap: keep Tab within the modal panel while it's open.
146    palette.addEventListener("keydown", function (e) {
147      if (isOpen() && panel) trapTab(panel, e);
148    });
149
150    input.addEventListener("keydown", function (e) {
151      if (e.key === "ArrowDown") {
152        e.preventDefault();
153        if (current.length) setActive((active + 1) % current.length);
154        scrollActiveIntoView();
155      } else if (e.key === "ArrowUp") {
156        e.preventDefault();
157        if (current.length) setActive((active - 1 + current.length) % current.length);
158        scrollActiveIntoView();
159      } else if (e.key === "Enter") {
160        e.preventDefault();
161        var rows = resultsEl.querySelectorAll(".nt-palette__result");
162        if (rows[active]) window.location.href = rows[active].href;
163      }
164    });
165
166    function scrollActiveIntoView() {
167      var row = resultsEl.querySelectorAll(".nt-palette__result")[active];
168      if (row) row.scrollIntoView({ block: "nearest" });
169    }
170
171    // backdrop click closes; clicks on the panel are contained
172    palette.addEventListener("click", function (e) {
173      if (e.target === palette) close();
174    });
175    var panel = palette.querySelector(".nt-palette__panel");
176    if (panel)
177      panel.addEventListener("click", function (e) {
178        e.stopPropagation();
179      });
180
181    // global key handling + triggers
182    window.addEventListener("keydown", function (e) {
183      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
184        e.preventDefault();
185        isOpen() ? close() : open(document.activeElement);
186      } else if (e.key === "Escape" && isOpen()) {
187        close();
188      }
189    });
190
191    Array.prototype.forEach.call(document.querySelectorAll("[data-nt-palette-open]"), function (btn) {
192      btn.addEventListener("click", function (e) {
193        e.preventDefault();
194        open(btn);
195      });
196    });
197
198    // 🤖 The input-style search buttons auto-launch the modal on focus (keyboard
199    // tab-in or click). Guarded so restoring focus on close doesn't re-open it.
200    Array.prototype.forEach.call(document.querySelectorAll(".nt-topnav__search, .nt-home__search"), function (btn) {
201      btn.addEventListener("focus", function () {
202        if (suppressAutoOpen || isOpen()) return;
203        open(btn);
204      });
205    });
206  }
207
208  function escapeHtml(s) {
209    return String(s).replace(/[&<>"']/g, function (c) {
210      return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c];
211    });
212  }
213
214  /* ---------------- TOC smooth-scroll + scroll-spy ---------------- */
215  function scrollToId(id) {
216    var el = document.getElementById(id);
217    if (!el) return;
218    window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - HEADER_OFFSET, behavior: "smooth" });
219  }
220
221  function initToc() {
222    // Right-rail docs TOC uses data-sec; legacy left rail uses #anchor hrefs.
223    var links = [];
224    Array.prototype.forEach.call(document.querySelectorAll(".nt-toc__link[data-sec]"), function (a) {
225      links.push({ a: a, id: a.getAttribute("data-sec") });
226      a.addEventListener("click", function (e) {
227        e.preventDefault();
228        scrollToId(a.getAttribute("data-sec"));
229        history.replaceState(null, "", "#" + a.getAttribute("data-sec"));
230      });
231    });
232    Array.prototype.forEach.call(document.querySelectorAll("#TableOfContents a[href^='#']"), function (a) {
233      var id = a.getAttribute("href").slice(1);
234      links.push({ a: a, id: id });
235      a.addEventListener("click", function (e) {
236        e.preventDefault();
237        scrollToId(id);
238        history.replaceState(null, "", "#" + id);
239      });
240    });
241    if (!links.length) return;
242
243    var activeClass = "is-active";
244    function spy() {
245      var best = -1;
246      for (var i = 0; i < links.length; i++) {
247        var el = document.getElementById(links[i].id);
248        if (el && el.getBoundingClientRect().top < HEADER_OFFSET + 16) best = i;
249      }
250      if (best < 0) best = 0;
251      links.forEach(function (l, i) {
252        l.a.classList.toggle(activeClass, i === best);
253        l.a.classList.toggle("active", i === best); // legacy TOC styling hook
254      });
255    }
256    window.addEventListener("scroll", spy, { passive: true });
257    spy();
258  }
259
260  /* ---------------- Code-block copy chrome ---------------- */
261  function initCodeCopy() {
262    Array.prototype.forEach.call(document.querySelectorAll(".contents pre"), function (pre) {
263      if (pre.closest(".nt-codeblock")) return;
264      var wrap = document.createElement("div");
265      wrap.className = "nt-codeblock";
266      var bar = document.createElement("div");
267      bar.className = "nt-codeblock__bar";
268
269      var code = pre.querySelector("code");
270      var lang = "";
271      if (code) {
272        var m = (code.className || "").match(/language-([\w-]+)/);
273        if (m) lang = m[1];
274      }
275      var label = document.createElement("span");
276      label.className = "nt-codeblock__lang";
277      label.textContent = lang || "code";
278
279      var btn = document.createElement("button");
280      btn.className = "nt-codeblock__copy";
281      btn.type = "button";
282      btn.innerHTML = '<span class="ms">content_copy</span><span>Copy</span>';
283      btn.addEventListener("click", function () {
284        var text = (code || pre).innerText;
285        var done = function () {
286          btn.querySelector("span:last-child").textContent = "Copied";
287          btn.querySelector(".ms").textContent = "check";
288          setTimeout(function () {
289            btn.querySelector("span:last-child").textContent = "Copy";
290            btn.querySelector(".ms").textContent = "content_copy";
291          }, 1600);
292        };
293        if (navigator.clipboard && navigator.clipboard.writeText) {
294          navigator.clipboard.writeText(text).then(done, function () {});
295        }
296      });
297
298      bar.appendChild(label);
299      bar.appendChild(btn);
300      pre.parentNode.insertBefore(wrap, pre);
301      wrap.appendChild(bar);
302      wrap.appendChild(pre);
303    });
304  }
305
306  /* ---------------- Callout icons on blockquotes ---------------- */
307  function initCallouts() {
308    var infoIcon =
309      '<svg aria-hidden="true" focusable="false" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 18a8 8 0 1 1 0-16 8 8 0 0 1 0 16Zm0-1.6A6.4 6.4 0 1 0 10 3.6a6.4 6.4 0 0 0 0 12.8ZM9.1 8.8h1.8v5H9.1v-5Zm.9-3.1a1.05 1.05 0 1 1 0 2.1 1.05 1.05 0 0 1 0-2.1Z" fill="currentColor"/></svg>'; // 🤖 decorative
310    Array.prototype.forEach.call(document.querySelectorAll(".contents blockquote"), function (bq) {
311      if (bq.querySelector(":scope > svg")) return;
312      var wrapper = document.createElement("div");
313      while (bq.firstChild) wrapper.appendChild(bq.firstChild);
314      var span = document.createElement("span");
315      span.innerHTML = infoIcon;
316      bq.appendChild(span.firstChild);
317      bq.appendChild(wrapper);
318    });
319  }
320
321  /* ---------------- Mobile popover menu + article-nav drawer ---------------- */
322  function initMobileNav() {
323    // 🤖 Article side-nav drawer (docs article pages) — its own toggle button.
324    var navBtn = document.querySelector(".nt-topnav__nav-btn");
325    if (navBtn) {
326      navBtn.addEventListener("click", function () {
327        var open = document.body.classList.toggle("nt-nav-open");
328        navBtn.setAttribute("aria-expanded", open ? "true" : "false");
329      });
330    }
331
332    // 🤖 "Toggle navigation" popover: search launcher + section actions, focus-trapped.
333    var menuBtn = document.querySelector(".nt-topnav__menu-btn");
334    var popover = document.getElementById("nt-nav-popover");
335    if (!menuBtn || !popover) return;
336
337    function openPopover() {
338      popover.hidden = false;
339      menuBtn.setAttribute("aria-expanded", "true");
340      var items = focusable(popover);
341      if (items.length) items[0].focus();
342      document.addEventListener("click", onDocClick, true);
343    }
344    function closePopover(restoreFocus) {
345      if (popover.hidden) return;
346      popover.hidden = true;
347      menuBtn.setAttribute("aria-expanded", "false");
348      document.removeEventListener("click", onDocClick, true);
349      if (restoreFocus !== false) menuBtn.focus();
350    }
351    function onDocClick(e) {
352      if (popover.contains(e.target) || menuBtn.contains(e.target)) return;
353      closePopover(false);
354    }
355
356    menuBtn.addEventListener("click", function () {
357      popover.hidden ? openPopover() : closePopover();
358    });
359    popover.addEventListener("keydown", function (e) {
360      if (popover.hidden) return;
361      if (e.key === "Escape") {
362        e.preventDefault();
363        closePopover();
364      } else {
365        trapTab(popover, e);
366      }
367    });
368  }
369
370  /* ---------------- Skip navigation ---------------- */
371  function initSkipNav() {
372    // 🤖 Move focus (and scroll) to the article main region.
373    Array.prototype.forEach.call(document.querySelectorAll("[data-nt-skip-nav]"), function (btn) {
374      btn.addEventListener("click", function () {
375        var main = document.getElementById("nt-doc-main") || document.querySelector(".nt-doc-main");
376        if (!main) return;
377        document.body.classList.remove("nt-nav-open");
378        main.focus();
379        main.scrollIntoView();
380      });
381    });
382  }
383
384  function init() {
385    initPalette();
386    initCallouts();
387    initCodeCopy();
388    initToc();
389    initMobileNav();
390    initSkipNav();
391  }
392
393  if (document.readyState === "loading") {
394    document.addEventListener("DOMContentLoaded", init);
395  } else {
396    init();
397  }
398})();

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.