Files
bookhoard/web/src/api-explorer-docs.ts
T
John O'Keefe 77cd268f4e style: format web/src with oxfmt defaults (via vp fmt)
One-time mechanical sweep, isolated from any functional change.
- oxfmt defaults: double quotes, semicolons, 2-space indent, LF,
  printWidth 100, trailing commas, bracket spacing, arrow parens.
- vite.config.ts gains an fmt.ignorePatterns block for web/static:
  vendored/minified assets (htmx.min.js, compiled css) must never
  be reformatted.
- Verified: rebuild after the sweep produces byte-identical JS/CSS
  bundles; only sourcemaps changed (they embed sourcesContent).
2026-10-21 13:18:24 -04:00

161 lines
4.8 KiB
TypeScript

import { Alpine } from "./alpine";
import { getToken } from "./storage";
let endpointPath = "";
let exampleResponse: unknown = null;
function initAPIExplorerDoc(path: string, response: string): void {
endpointPath = path;
exampleResponse = JSON.parse(response);
}
function showDocMode(mode: "mock" | "real"): void {
const mockBtn = document.getElementById("mock-btn");
const realBtn = document.getElementById("real-btn");
const responseBody = document.getElementById("response-body");
const responseStatus = document.getElementById("response-status");
const responseTime = document.getElementById("response-time");
const apiResponse = document.querySelector(".api-response");
const requestBody = document.getElementById("request-body") as HTMLTextAreaElement;
const tryItOut = document.getElementById("try-it-out");
if (
!mockBtn ||
!realBtn ||
!responseBody ||
!responseStatus ||
!responseTime ||
!apiResponse ||
!requestBody ||
!tryItOut
)
return;
if (mode === "mock") {
mockBtn.classList.add("bg-accent", "text-white");
mockBtn.classList.remove("bg-background-primary", "text-text-primary");
realBtn.classList.remove("bg-accent", "text-white");
realBtn.classList.add("bg-background-primary", "text-text-primary");
apiResponse.classList.remove("hidden");
requestBody.readOnly = true;
tryItOut.classList.add("hidden");
responseBody.textContent = JSON.stringify(exampleResponse, null, 2);
responseStatus.textContent = "200 OK";
responseTime.textContent = "Mock";
} else {
realBtn.classList.add("bg-accent", "text-white");
realBtn.classList.remove("bg-background-primary", "text-text-primary");
mockBtn.classList.remove("bg-accent", "text-white");
mockBtn.classList.add("bg-background-primary", "text-text-primary");
apiResponse.classList.add("hidden");
requestBody.readOnly = false;
tryItOut.classList.remove("hidden");
}
}
async function tryDocEndpoint(): Promise<void> {
const methodSelect = document.getElementById("http-method") as HTMLSelectElement;
const requestBody = document.getElementById("request-body") as HTMLTextAreaElement;
const responseBody = document.getElementById("response-body");
const responseStatus = document.getElementById("response-status");
const responseTime = document.getElementById("response-time");
const apiResponse = document.querySelector(".api-response");
if (
!methodSelect ||
!requestBody ||
!responseBody ||
!responseStatus ||
!responseTime ||
!apiResponse
)
return;
const method = methodSelect.value;
const body = requestBody.value;
const startTime = Date.now();
try {
const token = getToken();
if (!token) {
throw new Error("No authentication token found");
}
const response = await fetch(endpointPath, {
method: method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: ["GET", "DELETE"].includes(method) ? undefined : body,
});
const duration = Date.now() - startTime;
const data = await response.json();
responseStatus.textContent = `${response.status} (${response.statusText})`;
responseTime.textContent = `${duration}ms`;
responseBody.textContent = JSON.stringify(data, null, 2);
apiResponse.classList.remove("hidden");
} catch (error) {
responseStatus.textContent = "Error";
responseBody.textContent = (error as Error).message;
apiResponse.classList.remove("hidden");
}
}
function copyDocRequest(): void {
const requestBody = document.getElementById("request-body") as HTMLTextAreaElement;
if (requestBody) {
navigator.clipboard.writeText(requestBody.value);
}
}
function copyDocResponse(): void {
const responseBody = document.getElementById("response-body");
if (responseBody) {
navigator.clipboard.writeText(responseBody.textContent || "");
}
}
function generateDocCURL(): void {
const methodSelect = document.getElementById("http-method") as HTMLSelectElement;
const requestBody = document.getElementById("request-body") as HTMLTextAreaElement;
if (!methodSelect || !requestBody) return;
const method = methodSelect.value;
const body = requestBody.value;
const token = getToken();
let curl = `curl -X ${method} \\n -H "Content-Type: application/json" \\n -H "Authorization: Bearer ${token}"`;
if (!["GET", "DELETE"].includes(method) && body.trim()) {
curl += ` \\n -d '${body}'`;
}
curl += ` \\n ${endpointPath}`;
navigator.clipboard.writeText(curl);
}
export {
copyDocRequest,
copyDocResponse,
generateDocCURL,
initAPIExplorerDoc,
showDocMode,
tryDocEndpoint,
};
Alpine.data("apiExplorerDoc", () => ({
copyDocRequest,
copyDocResponse,
generateDocCURL,
initAPIExplorerDoc,
showDocMode,
tryDocEndpoint,
}));