143 lines
6.0 KiB
JavaScript
143 lines
6.0 KiB
JavaScript
(() => {
|
|
"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("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
|
|
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.";
|
|
});
|
|
})();
|