mirror of
https://gitlab.com/vyla-entertainment/documentation.git
synced 2026-11-09 20:17:20 -05:00
653 lines
23 KiB
JavaScript
653 lines
23 KiB
JavaScript
var THEME_KEY = "vyla-theme";
|
||
var ICONS = {
|
||
system: '<rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/>',
|
||
light: '<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M6.3 17.7l-1.4 1.4M19.1 4.9l-1.4 1.4"/>',
|
||
dark: '<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>',
|
||
search: '<circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/>',
|
||
menu: '<path d="M4 6h16M4 12h10M4 18h16"/>',
|
||
doc: '<path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5M9 13h6M9 17h6"/>',
|
||
hash: '<path d="M4 9h16M4 15h16M10 3 8 21M16 3l-2 18"/>',
|
||
copy: '<rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V6a2 2 0 0 1 2-2h9"/>',
|
||
check: '<path d="m5 12 5 5 9-10"/>',
|
||
chevron: '<path d="m6 9 6 6 6-6"/>'
|
||
};
|
||
var searchData = null;
|
||
var searchResults = [];
|
||
var searchActive = 0;
|
||
|
||
function icon(name) {
|
||
return '<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + ICONS[name] + "</svg>";
|
||
}
|
||
|
||
function esc(s) {
|
||
return String(s).replace(/[&<>"]/g, function (c) {
|
||
return { "&": "&", "<": "<", ">": ">", '"': """ }[c];
|
||
});
|
||
}
|
||
|
||
function getThemeMode() {
|
||
var v = localStorage.getItem(THEME_KEY);
|
||
return v === "light" || v === "dark" ? v : "system";
|
||
}
|
||
|
||
function applyTheme() {
|
||
var mode = getThemeMode();
|
||
var dark = mode === "dark" || (mode === "system" && window.matchMedia("(prefers-color-scheme: dark)").matches);
|
||
document.documentElement.classList.toggle("dark", dark);
|
||
var logo = document.getElementById("brand-logo");
|
||
if (logo) logo.src = dark ? "/assets/images/logo.png" : "/assets/images/dark_logo.png";
|
||
}
|
||
|
||
function setTheme(mode) {
|
||
if (mode === "system") localStorage.removeItem(THEME_KEY);
|
||
else localStorage.setItem(THEME_KEY, mode);
|
||
applyTheme();
|
||
refreshThemeUI();
|
||
var menu = document.getElementById("theme-menu");
|
||
if (menu) menu.classList.remove("open");
|
||
}
|
||
|
||
function toggleThemeMenu(e) {
|
||
e.stopPropagation();
|
||
document.getElementById("theme-menu").classList.toggle("open");
|
||
}
|
||
|
||
function refreshThemeUI() {
|
||
var mode = getThemeMode();
|
||
var btn = document.getElementById("theme-toggle");
|
||
if (btn) btn.innerHTML = icon(mode);
|
||
document.querySelectorAll("#theme-menu button, #theme-seg button").forEach(function (b) {
|
||
b.classList.toggle("on", b.getAttribute("data-mode") === mode);
|
||
});
|
||
}
|
||
|
||
function buildThemeControls() {
|
||
var btn = document.getElementById("theme-toggle");
|
||
var links = document.querySelector("#navbar .links");
|
||
if (!btn || !links) return;
|
||
var wrap = document.createElement("div");
|
||
wrap.className = "theme-wrap";
|
||
btn.parentNode.insertBefore(wrap, btn);
|
||
wrap.appendChild(btn);
|
||
var modes = ["system", "light", "dark"];
|
||
var menu = document.createElement("div");
|
||
menu.id = "theme-menu";
|
||
menu.innerHTML = modes.map(function (m) {
|
||
return '<button type="button" data-mode="' + m + '">' + icon(m) + "<span>" + m.charAt(0).toUpperCase() + m.slice(1) + '</span><i class="check">✓</i></button>';
|
||
}).join("");
|
||
wrap.appendChild(menu);
|
||
var seg = document.createElement("div");
|
||
seg.id = "theme-seg";
|
||
seg.innerHTML = modes.map(function (m) {
|
||
return '<button type="button" data-mode="' + m + '" aria-label="' + m + '">' + icon(m) + "</button>";
|
||
}).join("");
|
||
links.appendChild(seg);
|
||
[menu, seg].forEach(function (el) {
|
||
el.addEventListener("click", function (e) {
|
||
var b = e.target.closest("button");
|
||
if (b) setTheme(b.getAttribute("data-mode"));
|
||
});
|
||
});
|
||
refreshThemeUI();
|
||
}
|
||
|
||
function toggleSidebar() {
|
||
var sidebar = document.getElementById("sidebar");
|
||
var nav = document.getElementById("navbar");
|
||
var top = Math.max(0, Math.round(nav.getBoundingClientRect().bottom));
|
||
sidebar.style.setProperty("--sidebar-top", top + "px");
|
||
var open = sidebar.classList.toggle("open");
|
||
document.body.classList.toggle("nav-open", open);
|
||
}
|
||
|
||
function findActiveTab(root) {
|
||
var hrefs = {};
|
||
root.querySelectorAll("#sidebar .group li a").forEach(function (a) {
|
||
hrefs[a.getAttribute("href")] = true;
|
||
});
|
||
var tabs = root.querySelectorAll("#navbar .tabs a");
|
||
for (var i = 0; i < tabs.length; i++) {
|
||
if (hrefs[tabs[i].getAttribute("href")]) return tabs[i];
|
||
}
|
||
return root.querySelector("#navbar .tabs a.active");
|
||
}
|
||
|
||
function fixActiveTab() {
|
||
var current = findActiveTab(document);
|
||
if (!current) return;
|
||
document.querySelectorAll("#navbar .tabs a").forEach(function (a) {
|
||
a.classList.toggle("active", a === current);
|
||
});
|
||
}
|
||
|
||
function activeLink() {
|
||
return document.querySelector("#sidebar .group:not(.mobile-tabs) li a.active");
|
||
}
|
||
|
||
function activeGroupTitle() {
|
||
var a = activeLink();
|
||
var g = a && a.closest(".group");
|
||
var t = g && g.querySelector(".group-title");
|
||
return t ? t.textContent.trim() : "";
|
||
}
|
||
|
||
function buildMobileBar() {
|
||
var nav = document.getElementById("navbar");
|
||
var sidebar = document.getElementById("sidebar");
|
||
if (!nav) return;
|
||
var a = activeLink();
|
||
var group = activeGroupTitle();
|
||
var bar = document.createElement("div");
|
||
bar.id = "mobile-bar";
|
||
bar.innerHTML =
|
||
'<button id="mobile-toggle" type="button" aria-label="Toggle navigation" onclick="toggleSidebar()">' + icon("menu") + "</button>" +
|
||
(group ? "<span>" + esc(group) + '</span><span class="sep">›</span>' : "") +
|
||
'<span class="crumb-current">' + esc(a ? a.textContent.trim() : document.title) + "</span>";
|
||
nav.parentNode.insertBefore(bar, nav.nextSibling);
|
||
var tabs = document.querySelectorAll("#navbar .tabs a");
|
||
if (sidebar && tabs.length) {
|
||
var currentTab = document.querySelector("#navbar .tabs a.active") || tabs[0];
|
||
var g = document.createElement("div");
|
||
g.className = "mobile-tabs";
|
||
g.innerHTML =
|
||
'<button type="button" class="mt-btn" aria-expanded="false"><span>' + esc(currentTab.textContent.trim()) + "</span>" + icon("chevron") + "</button>" +
|
||
'<div class="mt-menu">' + Array.prototype.map.call(tabs, function (t) {
|
||
return '<a href="' + t.getAttribute("href") + '"' + (t === currentTab ? ' class="on"' : "") + ">" + esc(t.textContent.trim()) + icon("check") + "</a>";
|
||
}).join("") + "</div>";
|
||
sidebar.insertBefore(g, sidebar.firstChild);
|
||
g.querySelector(".mt-btn").addEventListener("click", function () {
|
||
g.classList.toggle("open");
|
||
});
|
||
document.addEventListener("click", function (e) {
|
||
if (!g.contains(e.target)) g.classList.remove("open");
|
||
});
|
||
g.querySelectorAll(".mt-menu a").forEach(function (a) {
|
||
a.addEventListener("click", function (e) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
var href = a.getAttribute("href");
|
||
g.querySelectorAll(".mt-menu a").forEach(function (link) {
|
||
link.classList.remove("on");
|
||
});
|
||
a.classList.add("on");
|
||
g.querySelector(".mt-btn span").textContent = a.textContent.trim();
|
||
g.classList.remove("open");
|
||
var sidebar = document.getElementById("sidebar");
|
||
if (sidebar) {
|
||
var mobileTabs = sidebar.querySelector(".mobile-tabs");
|
||
var nav = document.getElementById("navbar");
|
||
var top = Math.max(0, Math.round(nav.getBoundingClientRect().bottom));
|
||
sidebar.style.setProperty("--sidebar-top", top + "px");
|
||
fetch(href)
|
||
.then(function (r) { return r.text(); })
|
||
.then(function (html) {
|
||
var doc = new DOMParser().parseFromString(html, "text/html");
|
||
var newSidebar = doc.querySelector("#sidebar");
|
||
if (newSidebar) {
|
||
var groups = newSidebar.querySelectorAll(".group");
|
||
sidebar.innerHTML = "";
|
||
if (mobileTabs) sidebar.appendChild(mobileTabs);
|
||
groups.forEach(function (group) {
|
||
sidebar.appendChild(group.cloneNode(true));
|
||
});
|
||
addEyebrow();
|
||
}
|
||
})
|
||
.catch(function () { });
|
||
if (!sidebar.classList.contains("open")) {
|
||
sidebar.classList.add("open");
|
||
document.body.classList.add("nav-open");
|
||
}
|
||
}
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
function addEyebrow() {
|
||
var h1 = document.querySelector("main.prose h1");
|
||
var group = activeGroupTitle();
|
||
if (!h1 || !group) return;
|
||
var el = document.createElement("div");
|
||
el.className = "eyebrow";
|
||
el.textContent = group;
|
||
h1.parentNode.insertBefore(el, h1);
|
||
}
|
||
|
||
function addCopyButtons() {
|
||
document.querySelectorAll(".code-group").forEach(function (cg) {
|
||
if (cg.querySelector(".cg-copy-btn")) return;
|
||
var b = document.createElement("button");
|
||
b.type = "button";
|
||
b.className = "cg-copy-btn";
|
||
b.setAttribute("aria-label", "Copy");
|
||
b.innerHTML = icon("copy");
|
||
b.onclick = function () {
|
||
var activePre = cg.querySelector(".code-group-panel.active pre");
|
||
if (!activePre) return;
|
||
navigator.clipboard.writeText(activePre.innerText).then(function () {
|
||
b.innerHTML = icon("check");
|
||
setTimeout(function () { b.innerHTML = icon("copy"); }, 1500);
|
||
});
|
||
};
|
||
cg.appendChild(b);
|
||
});
|
||
|
||
document.querySelectorAll("main.prose pre").forEach(function (pre) {
|
||
if (pre.closest(".code-group")) return;
|
||
if (pre.parentNode.classList.contains("code-wrap")) return;
|
||
var wrap = document.createElement("div");
|
||
wrap.className = "code-wrap";
|
||
pre.parentNode.insertBefore(wrap, pre);
|
||
wrap.appendChild(pre);
|
||
var b = document.createElement("button");
|
||
b.type = "button";
|
||
b.className = "copy-btn";
|
||
b.setAttribute("aria-label", "Copy");
|
||
b.innerHTML = icon("copy");
|
||
b.onclick = function () {
|
||
navigator.clipboard.writeText(pre.innerText).then(function () {
|
||
b.innerHTML = icon("check");
|
||
setTimeout(function () { b.innerHTML = icon("copy"); }, 1500);
|
||
});
|
||
};
|
||
wrap.appendChild(b);
|
||
});
|
||
}
|
||
|
||
function bindScrollFade(box, getScroller) {
|
||
function update() {
|
||
var el = getScroller();
|
||
var max = el.scrollWidth - el.clientWidth;
|
||
var left = 0;
|
||
var right = 0;
|
||
if (max > 1) {
|
||
left = Math.min(el.scrollLeft, 56);
|
||
right = Math.min(Math.max(Math.round(max - el.scrollLeft), 0), 56);
|
||
}
|
||
box.style.setProperty("--fade-l", left + "px");
|
||
box.style.setProperty("--fade-r", right + "px");
|
||
}
|
||
box.addEventListener("scroll", update, true);
|
||
window.addEventListener("resize", update);
|
||
window.addEventListener("load", update);
|
||
update();
|
||
}
|
||
|
||
function initCodeFades() {
|
||
document.querySelectorAll("main.prose pre").forEach(function (pre) {
|
||
var code = pre.querySelector("code");
|
||
bindScrollFade(pre, function () {
|
||
return code && code.scrollWidth > code.clientWidth + 1 ? code : pre;
|
||
});
|
||
});
|
||
}
|
||
|
||
function wrapTables() {
|
||
document.querySelectorAll("main.prose table").forEach(function (table) {
|
||
if (table.parentNode.classList.contains("table-wrap")) return;
|
||
var wrap = document.createElement("div");
|
||
wrap.className = "table-wrap";
|
||
table.parentNode.insertBefore(wrap, table);
|
||
wrap.appendChild(table);
|
||
bindScrollFade(wrap, function () {
|
||
return wrap;
|
||
});
|
||
});
|
||
}
|
||
|
||
function initTocSpy() {
|
||
var links = Array.prototype.slice.call(document.querySelectorAll("aside#toc a[href^='#']"));
|
||
if (!links.length) return;
|
||
var heads = links.map(function (a) {
|
||
return document.getElementById(decodeURIComponent(a.getAttribute("href").slice(1)));
|
||
});
|
||
function update() {
|
||
var idx = 0;
|
||
heads.forEach(function (h, i) {
|
||
if (h && h.getBoundingClientRect().top < 140) idx = i;
|
||
});
|
||
links.forEach(function (a, i) { a.classList.toggle("active", i === idx); });
|
||
}
|
||
window.addEventListener("scroll", update, { passive: true });
|
||
update();
|
||
}
|
||
|
||
var SEARCH_KEY = "vyla-search-v1";
|
||
var searchPromise = null;
|
||
|
||
function getRecent() {
|
||
try { return JSON.parse(localStorage.getItem("vyla-recent-v2")) || []; } catch (e) { return []; }
|
||
}
|
||
|
||
function saveRecent(item) {
|
||
if (!item) return;
|
||
var slim = { title: item.title, crumbs: item.crumbs, href: item.href, type: item.type, text: "" };
|
||
var list = getRecent().filter(function (r) { return r.href !== slim.href; });
|
||
list.unshift(slim);
|
||
localStorage.setItem("vyla-recent-v2", JSON.stringify(list.slice(0, 5)));
|
||
}
|
||
|
||
function cleanText(s) {
|
||
return s.replace(/\s+/g, " ").trim();
|
||
}
|
||
|
||
function fetchDoc(href) {
|
||
return fetch(href)
|
||
.then(function (r) {
|
||
if (!r.ok) throw new Error(href);
|
||
return r.text();
|
||
})
|
||
.then(function (html) {
|
||
return { href: href, doc: new DOMParser().parseFromString(html, "text/html") };
|
||
})
|
||
.catch(function () {
|
||
return null;
|
||
});
|
||
}
|
||
|
||
function indexDoc(res) {
|
||
var doc = res.doc;
|
||
var href = res.href.split("#")[0];
|
||
var prose = doc.querySelector("main.prose");
|
||
if (!prose) return [];
|
||
var tabEl = findActiveTab(doc);
|
||
var activeEl = doc.querySelector("#sidebar li a.active");
|
||
var groupEl = activeEl && activeEl.closest(".group");
|
||
var groupTitleEl = groupEl && groupEl.querySelector(".group-title");
|
||
var h1 = prose.querySelector("h1");
|
||
var pageTitle = h1 ? cleanText(h1.textContent) : activeEl ? cleanText(activeEl.textContent) : href;
|
||
var base = [];
|
||
if (tabEl) base.push(cleanText(tabEl.textContent));
|
||
if (groupTitleEl) base.push(cleanText(groupTitleEl.textContent));
|
||
var current = { title: pageTitle, id: "", type: "page", crumbs: base.slice(), text: [] };
|
||
var entries = [current];
|
||
Array.prototype.forEach.call(prose.children, function (el) {
|
||
if (el.tagName === "H1") return;
|
||
if (el.tagName === "H2" || el.tagName === "H3") {
|
||
var title = cleanText(el.textContent);
|
||
current = { title: title, id: el.id, type: "heading", crumbs: base.concat([pageTitle, title]), text: [] };
|
||
entries.push(current);
|
||
return;
|
||
}
|
||
current.text.push(cleanText(el.textContent));
|
||
});
|
||
return entries.map(function (e) {
|
||
return {
|
||
title: e.title,
|
||
crumbs: e.crumbs,
|
||
href: e.id ? href + "#" + e.id : href,
|
||
type: e.type,
|
||
text: e.text.join(" ").slice(0, 4000)
|
||
};
|
||
});
|
||
}
|
||
|
||
function crawlSite() {
|
||
var seen = {};
|
||
var all = [];
|
||
function visit(hrefs) {
|
||
var fresh = hrefs.map(function (h) {
|
||
return new URL(h, window.location.href);
|
||
}).filter(function (u) {
|
||
if (u.origin !== window.location.origin || seen[u.pathname]) return false;
|
||
seen[u.pathname] = true;
|
||
return true;
|
||
}).map(function (u) {
|
||
return u.pathname;
|
||
});
|
||
return Promise.all(fresh.map(fetchDoc)).then(function (results) {
|
||
var next = [];
|
||
results.forEach(function (res) {
|
||
if (!res) return;
|
||
all = all.concat(indexDoc(res));
|
||
res.doc.querySelectorAll("#sidebar li a").forEach(function (a) {
|
||
var h = a.getAttribute("href");
|
||
if (h && h.charAt(0) !== "#") next.push(h);
|
||
});
|
||
});
|
||
return next.length ? visit(next) : all;
|
||
});
|
||
}
|
||
var start = Array.prototype.map.call(document.querySelectorAll("#navbar .tabs a"), function (a) {
|
||
return a.getAttribute("href");
|
||
});
|
||
start.push(window.location.pathname);
|
||
return visit(start).then(function () {
|
||
return all;
|
||
});
|
||
}
|
||
|
||
function loadSearchData() {
|
||
if (searchData) return Promise.resolve(searchData);
|
||
if (searchPromise) return searchPromise;
|
||
try {
|
||
var cached = JSON.parse(sessionStorage.getItem(SEARCH_KEY));
|
||
if (cached && cached.data && cached.data.length && Date.now() - cached.time < 600000) {
|
||
searchData = cached.data;
|
||
return Promise.resolve(searchData);
|
||
}
|
||
} catch (e) { }
|
||
searchPromise = crawlSite().then(function (all) {
|
||
searchData = all;
|
||
try { sessionStorage.setItem(SEARCH_KEY, JSON.stringify({ time: Date.now(), data: all })); } catch (e) { }
|
||
return all;
|
||
});
|
||
return searchPromise;
|
||
}
|
||
|
||
function escRe(s) {
|
||
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||
}
|
||
|
||
function hl(text, tokens) {
|
||
if (!tokens.length) return esc(text);
|
||
var re = new RegExp("(" + tokens.map(escRe).join("|") + ")", "ig");
|
||
return text.split(re).map(function (part, i) {
|
||
return i % 2 ? '<b class="hit">' + esc(part) + "</b>" : esc(part);
|
||
}).join("");
|
||
}
|
||
|
||
function lower(it) {
|
||
if (!it._l) it._l = { t: it.title.toLowerCase(), c: it.crumbs.join(" ").toLowerCase(), x: (it.text || "").toLowerCase() };
|
||
return it._l;
|
||
}
|
||
|
||
function scoreEntry(it, tokens, q) {
|
||
var low = lower(it);
|
||
var score = 0;
|
||
for (var i = 0; i < tokens.length; i++) {
|
||
var t = tokens[i];
|
||
var inTitle = low.t.indexOf(t) !== -1;
|
||
var inCrumbs = low.c.indexOf(t) !== -1;
|
||
var inText = low.x.indexOf(t) !== -1;
|
||
if (!inTitle && !inCrumbs && !inText) return 0;
|
||
score += (inTitle ? 20 : 0) + (inCrumbs ? 4 : 0) + (inText ? 1 : 0);
|
||
}
|
||
if (low.t === q) score += 100;
|
||
else if (low.t.indexOf(q) === 0) score += 50;
|
||
else if (low.t.indexOf(q) !== -1) score += 25;
|
||
if (it.type === "page") score += 5;
|
||
return score;
|
||
}
|
||
|
||
function makeSnippet(it, tokens) {
|
||
var low = lower(it);
|
||
var inTitle = tokens.every(function (t) { return low.t.indexOf(t) !== -1; });
|
||
if (inTitle || !it.text) return "";
|
||
var hit = -1;
|
||
tokens.forEach(function (t) {
|
||
var k = low.x.indexOf(t);
|
||
if (k !== -1 && (hit === -1 || k < hit)) hit = k;
|
||
});
|
||
if (hit === -1) return "";
|
||
var start = Math.max(0, hit - 50);
|
||
var end = hit + 120;
|
||
return (start > 0 ? "…" : "") + it.text.slice(start, end) + (end < it.text.length ? "…" : "");
|
||
}
|
||
|
||
function renderSearch(keepActive) {
|
||
var q = document.getElementById("search-input").value.trim().toLowerCase();
|
||
var tokens = q.split(/\s+/).filter(Boolean);
|
||
var box = document.getElementById("search-results");
|
||
if (!keepActive) searchActive = 0;
|
||
if (tokens.length) {
|
||
if (!searchData) {
|
||
searchResults = [];
|
||
box.innerHTML = '<div class="search-label">Indexing pages...</div>';
|
||
return;
|
||
}
|
||
searchResults = searchData
|
||
.map(function (it) { return { it: it, s: scoreEntry(it, tokens, q) }; })
|
||
.filter(function (r) { return r.s > 0; })
|
||
.sort(function (a, b) { return b.s - a.s; })
|
||
.slice(0, 12)
|
||
.map(function (r) { return r.it; });
|
||
} else {
|
||
searchResults = getRecent();
|
||
}
|
||
if (!searchResults.length) {
|
||
box.innerHTML = '<div class="search-label">' + (tokens.length ? "No results found" : "No recent searches") + "</div>";
|
||
return;
|
||
}
|
||
box.innerHTML = (tokens.length ? "" : '<div class="search-label">Recent searches</div>') + searchResults.map(function (it, i) {
|
||
var snippet = tokens.length ? makeSnippet(it, tokens) : "";
|
||
var crumbs = it.crumbs.map(function (c) { return hl(c, tokens); }).join('<span class="sep">›</span>');
|
||
return '<a class="sr' + (i === searchActive ? " on" : "") + '" href="' + esc(it.href) + '" data-i="' + i + '">' +
|
||
icon(it.type === "heading" ? "hash" : "doc") +
|
||
'<div class="sr-body"><div class="t">' + esc(it.title) + "</div>" +
|
||
(snippet ? '<div class="s">' + hl(snippet, tokens) + "</div>" : "") +
|
||
'<div class="p">' + crumbs + "</div></div></a>";
|
||
}).join("");
|
||
if (keepActive) {
|
||
var on = box.querySelector(".sr.on");
|
||
if (on) on.scrollIntoView({ block: "nearest" });
|
||
}
|
||
}
|
||
|
||
function openSearch() {
|
||
var input = document.getElementById("search-input");
|
||
document.getElementById("search-modal").classList.add("open");
|
||
document.body.style.overflow = "hidden";
|
||
input.value = "";
|
||
renderSearch(false);
|
||
loadSearchData().then(function () { renderSearch(false); });
|
||
input.focus();
|
||
}
|
||
|
||
function closeSearch() {
|
||
document.getElementById("search-modal").classList.remove("open");
|
||
document.body.style.overflow = "";
|
||
}
|
||
|
||
function onSearchKey(e) {
|
||
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") {
|
||
e.preventDefault();
|
||
openSearch();
|
||
return;
|
||
}
|
||
var modal = document.getElementById("search-modal");
|
||
if (!modal || !modal.classList.contains("open")) return;
|
||
if (e.key === "Escape") {
|
||
closeSearch();
|
||
} else if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
||
e.preventDefault();
|
||
var n = searchResults.length || 1;
|
||
searchActive = (searchActive + (e.key === "ArrowDown" ? 1 : -1) + n) % n;
|
||
renderSearch(true);
|
||
} else if (e.key === "Enter" && searchResults[searchActive]) {
|
||
var item = searchResults[searchActive];
|
||
saveRecent(item);
|
||
closeSearch();
|
||
window.location.href = item.href;
|
||
}
|
||
}
|
||
|
||
function buildSearch() {
|
||
var trigger = document.getElementById("search-trigger");
|
||
if (trigger) trigger.innerHTML = icon("search") + "<span>Search...</span><kbd>Ctrl K</kbd>";
|
||
var modal = document.createElement("div");
|
||
modal.id = "search-modal";
|
||
modal.innerHTML =
|
||
'<div class="search-backdrop"></div><div class="search-panel"><div class="search-input-row">' + icon("search") +
|
||
'<input id="search-input" type="text" placeholder="Search..." autocomplete="off" spellcheck="false"></div>' +
|
||
'<div id="search-results"></div><div class="search-footer"><span><kbd>↑</kbd><kbd>↓</kbd>Select</span><span><kbd>↵</kbd>Open</span><span class="close-hint"><kbd>esc</kbd>Close</span></div></div>';
|
||
document.body.appendChild(modal);
|
||
modal.querySelector(".search-backdrop").addEventListener("click", closeSearch);
|
||
document.getElementById("search-input").addEventListener("input", function () { renderSearch(false); });
|
||
document.getElementById("search-results").addEventListener("click", function (e) {
|
||
var a = e.target.closest(".sr");
|
||
if (a) {
|
||
saveRecent(searchResults[Number(a.getAttribute("data-i"))]);
|
||
closeSearch();
|
||
}
|
||
});
|
||
document.addEventListener("keydown", onSearchKey);
|
||
}
|
||
|
||
applyTheme();
|
||
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", applyTheme);
|
||
|
||
function dismissBanner() {
|
||
var banner = document.getElementById("banner");
|
||
if (banner) banner.setAttribute("data-dismissed", "true");
|
||
document.body.classList.add("banner-dismissed");
|
||
localStorage.setItem("vyla-banner-dismissed", "true");
|
||
}
|
||
|
||
function cgSwitch(btn) {
|
||
var group = btn.closest(".code-group");
|
||
var index = btn.getAttribute("data-tab-index");
|
||
group.querySelectorAll(".code-group-tab").forEach(function (t) {
|
||
t.classList.toggle("active", t === btn);
|
||
});
|
||
group.querySelectorAll(".code-group-panel").forEach(function (p, i) {
|
||
p.classList.toggle("active", String(i) === index);
|
||
});
|
||
window.dispatchEvent(new Event("resize"));
|
||
}
|
||
|
||
document.addEventListener("DOMContentLoaded", function () {
|
||
if (localStorage.getItem("vyla-banner-dismissed") === "true") {
|
||
var banner = document.getElementById("banner");
|
||
if (banner) banner.setAttribute("data-dismissed", "true");
|
||
document.body.classList.add("banner-dismissed");
|
||
}
|
||
applyTheme();
|
||
fixActiveTab();
|
||
buildMobileBar();
|
||
addEyebrow();
|
||
addCopyButtons();
|
||
initCodeFades();
|
||
wrapTables();
|
||
buildThemeControls();
|
||
buildSearch();
|
||
setTimeout(loadSearchData, 1500);
|
||
initTocSpy();
|
||
document.addEventListener("click", function (e) {
|
||
var sidebar = document.getElementById("sidebar");
|
||
var menu = document.getElementById("theme-menu");
|
||
if (menu && !e.target.closest(".theme-wrap")) menu.classList.remove("open");
|
||
if (
|
||
sidebar &&
|
||
sidebar.classList.contains("open") &&
|
||
!sidebar.contains(e.target) &&
|
||
!e.target.closest("#mobile-toggle") &&
|
||
!e.target.closest(".theme-wrap") &&
|
||
!e.target.closest("#theme-seg")
|
||
) {
|
||
sidebar.classList.remove("open");
|
||
document.body.classList.remove("nav-open");
|
||
}
|
||
});
|
||
var sidebarLinks = document.querySelectorAll("#sidebar .group:not(.mobile-tabs) li a");
|
||
sidebarLinks.forEach(function (a) {
|
||
a.addEventListener("click", function () {
|
||
var sidebar = document.getElementById("sidebar");
|
||
if (sidebar && sidebar.classList.contains("open")) {
|
||
sidebar.classList.remove("open");
|
||
document.body.classList.remove("nav-open");
|
||
}
|
||
});
|
||
});
|
||
}); |