first init

This commit is contained in:
2026-08-14 00:13:56 +05:00
commit 7ae997f99e
14 changed files with 6310 additions and 0 deletions
+142
View File
@@ -0,0 +1,142 @@
(() => {
"use strict";
const list = document.querySelector("#endpoint-list");
const template = document.querySelector("#endpoint-template");
const search = document.querySelector("#search");
const filters = document.querySelector("#service-filters");
const count = document.querySelector("#endpoint-count");
const empty = document.querySelector("#empty");
const sidebar = document.querySelector("#sidebar");
const menu = document.querySelector("#menu");
let manifest;
let activeService = "all";
const escapeHtml = (value) => String(value)
.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
function curlFor(service, endpoint) {
const url = `${service.base_url}${endpoint.path.replaceAll(/{([^}]+)}/g, "<$1>")}`;
const lines = [`curl -X ${endpoint.method} '${url}'`];
if (endpoint.auth === "bearer" || endpoint.auth === "admin") {
lines.push(" -H 'Authorization: Bearer $TOKEN'");
} else if (endpoint.auth === "hmac") {
lines.push(" -H 'X-Api-Key: $API_KEY'",
" -H 'X-Api-Timestamp: $TIMESTAMP'",
" -H 'X-Api-Nonce: $UUID'",
" -H 'X-Api-Signature: $SIGNATURE'");
if (endpoint.method !== "GET") lines.push(" -H 'Idempotency-Key: $UUID'");
}
if (endpoint.body) {
lines.push(" -H 'Content-Type: application/json'",
` -d '${JSON.stringify(endpoint.body)}'`);
}
return lines.join(" \\\n");
}
function endpointId(service, endpoint) {
return `${service.id}-${endpoint.method.toLowerCase()}-${endpoint.path
.replace(/[{}]/g, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-|-$/g, "")}`;
}
function render() {
const query = search.value.trim().toLowerCase();
list.replaceChildren();
let shown = 0;
manifest.services.forEach((service) => {
service.endpoints.forEach((endpoint) => {
const haystack = `${service.name} ${endpoint.method} ${endpoint.path} ${endpoint.title} ${endpoint.description} ${endpoint.group}`.toLowerCase();
if ((activeService !== "all" && activeService !== service.id) || !haystack.includes(query)) return;
shown += 1;
const node = template.content.firstElementChild.cloneNode(true);
node.id = endpointId(service, endpoint);
node.dataset.service = service.id;
const summary = node.querySelector(".endpoint-summary");
const method = node.querySelector(".method");
method.textContent = endpoint.method;
method.classList.add(endpoint.method);
node.querySelector(".path").textContent = endpoint.path;
node.querySelector(".endpoint-title").textContent = endpoint.title;
node.querySelector(".auth-badge").textContent = endpoint.auth_label;
node.querySelector(".endpoint-description").textContent = endpoint.description;
node.querySelector(".endpoint-meta").innerHTML =
`<code>${escapeHtml(service.base_url)}</code><code>${escapeHtml(endpoint.group)}</code>`;
const curl = curlFor(service, endpoint);
node.querySelector(".endpoint-example").innerHTML =
`<div class="code-card"><div class="code-head"><span>cURL</span><button class="copy">Копировать</button></div><pre><code>${escapeHtml(curl)}</code></pre></div>`;
summary.addEventListener("click", () => {
const open = node.classList.toggle("open");
summary.setAttribute("aria-expanded", String(open));
if (open) history.replaceState(null, "", `#${node.id}`);
});
node.querySelector(".copy").addEventListener("click", (event) => copyText(event.currentTarget, curl));
list.append(node);
});
});
count.textContent = `${shown} / ${manifest.total_endpoints}`;
empty.hidden = shown !== 0;
}
function copyText(button, text) {
navigator.clipboard.writeText(text).then(() => {
const old = button.textContent;
button.textContent = "Скопировано";
window.setTimeout(() => { button.textContent = old; }, 1300);
});
}
function makeFilters() {
const choices = [["all", "Все"], ...manifest.services.map((service) => [service.id, service.name])];
choices.forEach(([id, label]) => {
const button = document.createElement("button");
button.className = `filter${id === "all" ? " active" : ""}`;
button.textContent = label;
button.addEventListener("click", () => {
activeService = id;
filters.querySelectorAll(".filter").forEach((item) => item.classList.toggle("active", item === button));
render();
});
filters.append(button);
});
}
document.querySelectorAll("[data-copy-target]").forEach((button) => {
button.addEventListener("click", () => copyText(button, document.querySelector(`#${button.dataset.copyTarget}`).innerText));
});
search.addEventListener("input", render);
document.addEventListener("keydown", (event) => {
if (event.key === "/" && document.activeElement !== search) {
event.preventDefault();
search.focus();
}
if (event.key === "Escape") sidebar.classList.remove("open");
});
menu.addEventListener("click", () => {
const open = sidebar.classList.toggle("open");
menu.setAttribute("aria-expanded", String(open));
});
sidebar.querySelectorAll("a").forEach((link) => link.addEventListener("click", () => sidebar.classList.remove("open")));
fetch("/api-manifest.json")
.then((response) => {
if (!response.ok) throw new Error(`manifest ${response.status}`);
return response.json();
})
.then((data) => {
manifest = data;
makeFilters();
render();
if (location.hash.startsWith("#messenger-") || location.hash.startsWith("#gateway-")) {
const target = document.querySelector(location.hash);
if (target) {
target.classList.add("open");
target.querySelector(".endpoint-summary").setAttribute("aria-expanded", "true");
target.scrollIntoView();
}
}
})
.catch(() => {
empty.hidden = false;
empty.textContent = "Не удалось загрузить API catalog.";
});
})();