Files
bookhoard/web/src/api-explorer.ts
T
john-okeefe dfd9cbcde7 feat(typescript): add feature modules for template conversion
- Add search.ts - header search with keyboard navigation
  - Debounced search with 300ms delay
  - Arrow key navigation through results
  - Escape to close, Enter to select
  - Library type icons and highlighting

- Add collections.ts - collection and rule management
  - Rule CRUD operations (create, update, delete)
  - Rule testing functionality
  - Bulk collection operations

- Add bookshelf.ts - book display and navigation
  - Library selection state management
  - Book viewing interactions
  - Pagination logic

- Add linking.ts - book matching and manual linking
  - Search and match functionality
  - Manual link modal
  - Bulk auto-link and suggestions

- Add api-explorer.ts - API testing interface
  - Request/response display
  - cURL command generation
  - History tracking

- Add admin.ts - admin dashboard actions
  - Library scan triggers
  - System statistics display
  - Profile management

- Add analytics.ts - analytics data loading
  - Chart.js integration
  - Daily reading minutes chart
  - Device usage and popular books display

- Add queue.ts - sync queue management
  - Process pending items
  - Clear failed/all items
  - Filter by status, type, device

- Add conflicts.ts - conflict resolution
  - Individual and bulk resolve operations
  - Winner device selection
  - Manual override inputs

- Add docs.ts - documentation search
  - Lunr.js search integration
  - Sidebar toggle for mobile
2026-02-18 16:40:51 -05:00

174 lines
5.6 KiB
TypeScript

interface ApiExplorerRequest {
method: string;
endpoint: string;
headers: Record<string, string>;
body?: string;
}
const requestHistory: ApiExplorerRequest[] = [];
function sendApiRequest(): void {
const method = (document.getElementById('api-method') as HTMLSelectElement)?.value || 'GET';
const endpoint = (document.getElementById('api-endpoint') as HTMLInputElement)?.value || '';
const bodyText = (document.getElementById('api-body') as HTMLTextAreaElement)?.value || '';
const token = localStorage.getItem('token');
const headers: Record<string, string> = {
'Content-Type': 'application/json'
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const request: ApiExplorerRequest = {
method,
endpoint,
headers,
body: bodyText || undefined
};
addToHistory(request);
const startTime = performance.now();
fetch(endpoint, {
method,
headers,
body: bodyText || undefined
})
.then(async response => {
const endTime = performance.now();
const duration = Math.round(endTime - startTime);
const responseText = await response.text();
let responseData: unknown;
try {
responseData = JSON.parse(responseText);
} catch {
responseData = responseText;
}
displayResponse(response, responseData, duration);
generateCurl(request);
})
.catch(error => {
displayError(error);
});
}
function displayResponse(response: Response, data: unknown, duration: number): void {
const container = document.getElementById('api-response');
if (!container) return;
const statusColor = response.ok ? 'var(--accent)' : 'var(--error)';
container.innerHTML = `
<div class="mb-4">
<span class="px-2 py-1 rounded text-sm" style="background-color: ${statusColor}; color: var(--bg-primary)">
${response.status} ${response.statusText}
</span>
<span class="text-sm ml-2" style="color: var(--text-secondary)">${duration}ms</span>
</div>
<pre class="p-4 rounded overflow-auto max-h-96" style="background-color: var(--bg-primary); color: var(--text-primary)">${JSON.stringify(data, null, 2)}</pre>
`;
}
function displayError(error: Error): void {
const container = document.getElementById('api-response');
if (!container) return;
container.innerHTML = `
<div class="p-4 rounded" style="background-color: var(--bg-primary)">
<p style="color: var(--error)">Error: ${error.message}</p>
</div>
`;
}
function generateCurl(request: ApiExplorerRequest): void {
const container = document.getElementById('curl-command');
if (!container) return;
let curl = `curl -X ${request.method} '${request.endpoint}'`;
Object.entries(request.headers).forEach(([key, value]) => {
curl += ` \\\n -H '${key}: ${value}'`;
});
if (request.body) {
curl += ` \\\n -d '${request.body}'`;
}
container.textContent = curl;
}
function addToHistory(request: ApiExplorerRequest): void {
requestHistory.unshift(request);
if (requestHistory.length > 20) {
requestHistory.pop();
}
renderHistory();
}
function renderHistory(): void {
const container = document.getElementById('request-history');
if (!container) return;
if (requestHistory.length === 0) {
container.innerHTML = '<p class="text-sm p-2" style="color: var(--text-secondary)">No requests yet</p>';
return;
}
container.innerHTML = requestHistory.slice(0, 10).map((req, i) => `
<div class="p-2 rounded cursor-pointer hover:bg-opacity-50 transition-colors"
style="background-color: var(--bg-secondary)"
onclick="window.loadFromHistory(${i})">
<span class="text-xs font-mono" style="color: ${req.method === 'GET' ? 'var(--accent)' : req.method === 'POST' ? 'var(--success)' : req.method === 'DELETE' ? 'var(--error)' : 'var(--text-primary)'}">${req.method}</span>
<span class="text-xs ml-2" style="color: var(--text-secondary)">${req.endpoint}</span>
</div>
`).join('');
}
function loadFromHistory(index: number): void {
const request = requestHistory[index];
if (!request) return;
const methodSelect = document.getElementById('api-method') as HTMLSelectElement;
const endpointInput = document.getElementById('api-endpoint') as HTMLInputElement;
const bodyInput = document.getElementById('api-body') as HTMLTextAreaElement;
if (methodSelect) methodSelect.value = request.method;
if (endpointInput) endpointInput.value = request.endpoint;
if (bodyInput) bodyInput.value = request.body || '';
}
function copyCurl(): void {
const curl = document.getElementById('curl-command')?.textContent;
if (curl) {
navigator.clipboard.writeText(curl);
if ((window as any).showToast?.success) {
(window as any).showToast.success('cURL copied to clipboard');
}
}
}
function formatJson(): void {
const bodyInput = document.getElementById('api-body') as HTMLTextAreaElement;
if (!bodyInput) return;
try {
const parsed = JSON.parse(bodyInput.value);
bodyInput.value = JSON.stringify(parsed, null, 2);
} catch {
if ((window as any).showToast?.error) {
(window as any).showToast.error('Invalid JSON');
}
}
}
(window as any).sendApiRequest = sendApiRequest;
(window as any).loadFromHistory = loadFromHistory;
(window as any).copyCurl = copyCurl;
(window as any).formatJson = formatJson;