Initial documentation
653
assets/app.js
Normal file
@@ -0,0 +1,653 @@
|
||||
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");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
1501
assets/base.css
Normal file
107
assets/highlight.css
Normal file
@@ -0,0 +1,107 @@
|
||||
pre code.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 1em;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
code.hljs {
|
||||
padding: 3px 5px;
|
||||
}
|
||||
|
||||
.hljs {
|
||||
color: #5e5e5e;
|
||||
background: #0d1117
|
||||
}
|
||||
|
||||
.hljs-doctag,
|
||||
.hljs-keyword,
|
||||
.hljs-meta .hljs-keyword,
|
||||
.hljs-template-tag,
|
||||
.hljs-template-variable,
|
||||
.hljs-type,
|
||||
.hljs-variable.language_ {
|
||||
color: #ff7b72
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-title.class_,
|
||||
.hljs-title.class_.inherited__,
|
||||
.hljs-title.function_ {
|
||||
color: #d2a8ff
|
||||
}
|
||||
|
||||
.hljs-attr,
|
||||
.hljs-attribute,
|
||||
.hljs-literal,
|
||||
.hljs-meta,
|
||||
.hljs-number,
|
||||
.hljs-operator,
|
||||
.hljs-variable,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-id {
|
||||
color: #79c0ff
|
||||
}
|
||||
|
||||
.hljs-regexp,
|
||||
.hljs-string,
|
||||
.hljs-meta .hljs-string {
|
||||
color: #a5d6ff
|
||||
}
|
||||
|
||||
.hljs-built_in,
|
||||
.hljs-symbol {
|
||||
color: #ffa657
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-code,
|
||||
.hljs-formula {
|
||||
color: #8b949e
|
||||
}
|
||||
|
||||
.hljs-name,
|
||||
.hljs-quote,
|
||||
.hljs-selector-tag,
|
||||
.hljs-selector-pseudo {
|
||||
color: #7ee787
|
||||
}
|
||||
|
||||
.hljs-subst {
|
||||
color: #5e5e5e
|
||||
}
|
||||
|
||||
.hljs-section {
|
||||
color: #1f6feb;
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
.hljs-bullet {
|
||||
color: #f2cc60
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
color: #5e5e5e;
|
||||
font-style: italic
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
color: #5e5e5e;
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
color: #aff5b4;
|
||||
background-color: #033a16
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
color: #ffdcd7;
|
||||
background-color: #67060c
|
||||
}
|
||||
BIN
assets/images/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
assets/images/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
assets/images/android-tv/site-preview.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
assets/images/android/site_preview.png
Normal file
|
After Width: | Height: | Size: 194 KiB |
BIN
assets/images/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
assets/images/dark_logo.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
assets/images/desktop-preview.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
assets/images/desktop/site_preview.png
Normal file
|
After Width: | Height: | Size: 1016 KiB |
BIN
assets/images/devices.png
Normal file
|
After Width: | Height: | Size: 441 KiB |
BIN
assets/images/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 360 B |
BIN
assets/images/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 652 B |
BIN
assets/images/favicon-96x96.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/images/favicon.ico
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
assets/images/header_logo.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
assets/images/logo.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
assets/images/partners/fmhy.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
assets/images/partners/piracy.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
assets/images/rounded-banner.png
Normal file
|
After Width: | Height: | Size: 3.9 MiB |
BIN
assets/images/thumbnail.png
Normal file
|
After Width: | Height: | Size: 3.3 MiB |
121
assets/mintlify-overrides.css
Normal file
@@ -0,0 +1,121 @@
|
||||
:root {
|
||||
--table-header-text: #000000;
|
||||
--vyla-black: #000000;
|
||||
--vyla-white: #ffffff;
|
||||
--vyla-border-light: rgba(0, 0, 0, 0.12);
|
||||
--vyla-border-dark: rgba(255, 255, 255, 0.16);
|
||||
--banner-bg: #ffffff;
|
||||
--banner-text: #000000;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--table-header-text: #ffffff;
|
||||
--banner-bg: #000000;
|
||||
--banner-text: #ffffff;
|
||||
}
|
||||
|
||||
table th {
|
||||
color: var(--table-header-text) !important;
|
||||
}
|
||||
|
||||
#banner,
|
||||
#banner *,
|
||||
#banner p,
|
||||
#banner a {
|
||||
color: var(--banner-text) !important;
|
||||
}
|
||||
|
||||
.data-\[is-opaque\=true\]\:bg-background-light[data-is-opaque=true] {
|
||||
background-color: rgba(255, 255, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
.data-\[is-opaque\=true\]\:dark\:bg-background-dark\/75:is(.dark *)[data-is-opaque=true] {
|
||||
background-color: rgba(0, 0, 0, 0.2) !important;
|
||||
}
|
||||
|
||||
#banner a {
|
||||
text-decoration: underline !important;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
#banner button {
|
||||
color: var(--banner-text) !important;
|
||||
}
|
||||
|
||||
#navbar a[href*="gitlab"],
|
||||
#navbar a[href*="discord"],
|
||||
#navbar a[href="/"] {
|
||||
border-radius: 999px !important;
|
||||
}
|
||||
|
||||
#navbar a[href="/"] {
|
||||
padding-left: 16px !important;
|
||||
padding-right: 16px !important;
|
||||
}
|
||||
|
||||
.dark\:border-gray-300\/\[0\.06\]:is(.dark *) {
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.dark\:ring-0:is(.dark *),
|
||||
.dark\:ring-1:is(.dark *),
|
||||
.ring-1,
|
||||
.ring-2 {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.dark\:hover\:\!border-primary-light:hover:is(.dark *) {
|
||||
background-color: transparent !important;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.hover\:\!border-primary:hover {
|
||||
background-color: transparent !important;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.dark\:border-white\/10:is(.dark *) {
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.dark\:bg-white\/10:is(.dark *) {
|
||||
background-color: rgba(25, 25, 25, 0.1) !important;
|
||||
}
|
||||
|
||||
html.dark .code-block-background {
|
||||
background-color: rgba(5, 5, 5) !important;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
html .code-block-background {
|
||||
background-color: rgba(250, 250, 250) !important;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
[data-fade-overlay] {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.border {
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.h-\[1\.5px\] {
|
||||
height: 0px !important;
|
||||
}
|
||||
|
||||
.prose :where(img):not(:where([class~=not-prose], [class~=not-prose] *)) {
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
html.dark [data-fade-overlay] {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.dark\:text-gray-500:is(.dark *) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.px-6 {
|
||||
padding-left: 0px !important;
|
||||
}
|
||||