- Use `json2.decode[json2.Any]` instead of `json2.raw_decode` - Add `@[required]` to procedure function signatures - Improve error handling for missing JSONRPC fields - Update `encode` to use `prettify: true` - Add checks for missing schema and content descriptor references
743 lines
34 KiB
HTML
743 lines
34 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>${spec.info.title} - API Documentation</title>
|
|
|
|
<!-- Bootstrap CSS -->
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
@include 'partials/theme_styles'
|
|
</head>
|
|
|
|
<body>
|
|
@include 'partials/navbar'
|
|
|
|
<div class="container mt-4">
|
|
<!-- Header -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<h1 class="display-4">${spec.info.title}</h1>
|
|
@if spec.info.description.len > 0
|
|
<p class="lead">${spec.info.description}</p>
|
|
@end
|
|
|
|
<!-- Service Information -->
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<p><strong>Version:</strong> <span class="badge bg-primary">${spec.info.version}</span></p>
|
|
@if spec.info.terms_of_service.len > 0
|
|
<p><strong>Terms of Service:</strong> <a href="${spec.info.terms_of_service}"
|
|
target="_blank">View Terms</a></p>
|
|
@end
|
|
</div>
|
|
<div class="col-md-6">
|
|
@if spec.info.contact.name.len > 0
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h6 class="card-title">Contact Information</h6>
|
|
@if spec.info.contact.name.len > 0
|
|
<p class="card-text"><strong>Name:</strong> ${spec.info.contact.name}</p>
|
|
@end
|
|
@if spec.info.contact.email.len > 0
|
|
<p class="card-text"><strong>Email:</strong> <a
|
|
href="mailto:${spec.info.contact.email}">${spec.info.contact.email}</a></p>
|
|
@end
|
|
@if spec.info.contact.url.len > 0
|
|
<p class="card-text"><strong>Website:</strong> <a href="${spec.info.contact.url}"
|
|
target="_blank">${spec.info.contact.url}</a></p>
|
|
@end
|
|
</div>
|
|
</div>
|
|
@end
|
|
|
|
@if spec.info.license.name.len > 0
|
|
<div class="card mt-2">
|
|
<div class="card-body">
|
|
<h6 class="card-title">License</h6>
|
|
@if spec.info.license.name.len > 0
|
|
<p class="card-text"><strong>License:</strong> ${spec.info.license.name}</p>
|
|
@end
|
|
@if spec.info.license.url.len > 0
|
|
<p class="card-text"><a href="${spec.info.license.url}" target="_blank">View License</a>
|
|
</p>
|
|
@end
|
|
</div>
|
|
</div>
|
|
@end
|
|
</div>
|
|
</div>
|
|
<hr>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table of Contents -->
|
|
@if spec.methods.len > 0 || spec.objects.len > 0
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="toc">
|
|
<div class="toc-header" aria-expanded="true" aria-controls="tocContent">
|
|
<h5 class="mb-0">Table of Contents</h5>
|
|
<span class="toc-toggle-icon">▼</span>
|
|
</div>
|
|
<div class="collapse show" id="tocContent">
|
|
<div class="mt-3">
|
|
@if spec.method_groups.len > 0
|
|
<h6>API Methods</h6>
|
|
@for group_idx, group in spec.method_groups
|
|
<div class="toc-group">
|
|
<div class="toc-group-header"
|
|
onclick="toggleTocGroup('toc-group-methods-${group_idx}')">
|
|
<div>
|
|
<span class="toc-group-toggle" id="toc-toggle-${group_idx}">▼</span>
|
|
${group.display_name} (${group.methods.len})
|
|
</div>
|
|
</div>
|
|
<div class="collapse show" id="toc-group-methods-${group_idx}">
|
|
<ul class="toc-group-methods">
|
|
@for method in group.methods
|
|
<li>
|
|
<a href="#method-${method.name}">${method.name}</a>
|
|
@if method.summary.len > 0
|
|
- ${method.summary}
|
|
@end
|
|
</li>
|
|
@end
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
@end
|
|
@end
|
|
|
|
@if spec.objects.len > 0
|
|
<h6>API Objects</h6>
|
|
<ul>
|
|
@for object in spec.objects
|
|
<li><a href="#object-${object.name}">${object.name.title()}</a> - ${object.description}
|
|
</li>
|
|
@end
|
|
</ul>
|
|
@end
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@end
|
|
|
|
<!-- API Methods (Grouped by Model/Actor) -->
|
|
@if spec.method_groups.len > 0
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<h2>API Methods</h2>
|
|
<p class="text-muted">Methods organized by groups</p>
|
|
|
|
@for group_idx, group in spec.method_groups
|
|
<div class="method-group-section">
|
|
<div class="method-group-header" onclick="toggleMethodGroup('group-${group_idx}')">
|
|
<h3>${group.display_name}</h3>
|
|
<span class="method-group-toggle" id="group-toggle-${group_idx}">▼</span>
|
|
</div>
|
|
<div class="collapse show method-group-content" id="group-${group_idx}">
|
|
@for method_idx, method in group.methods
|
|
<div class="card method-card mb-3" id="method-${method.name}">
|
|
<div class="card-header"
|
|
onclick="toggleMethodCard('method-body-${group_idx}-${method_idx}')">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<span class="collapsible-toggle" id="toggle-${group_idx}-${method_idx}">▼</span>
|
|
<h5 class="d-inline mb-0">${method.name}</h5>
|
|
</div>
|
|
</div>
|
|
<div class="method-endpoint mt-2 mb-2">POST ${method.endpoint_url}</div>
|
|
@if method.summary.len > 0
|
|
<p class="mb-0 mt-2 text-muted">${method.summary}</p>
|
|
@end
|
|
</div>
|
|
<div class="collapse" id="method-body-${group_idx}-${method_idx}">
|
|
<div class="card-body">
|
|
@if method.description.len > 0
|
|
<p class="card-text">${method.description}</p>
|
|
@end
|
|
|
|
<!-- Parameters -->
|
|
@if method.params.len > 0
|
|
<h6>Parameters:</h6>
|
|
<table class="table table-sm param-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Type</th>
|
|
<th>Required</th>
|
|
<th>Description</th>
|
|
<th>Example</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@for param in method.params
|
|
<tr>
|
|
<td><code>${param.name}</code></td>
|
|
<td><span class="badge bg-secondary">${param.type_info}</span></td>
|
|
@if param.required
|
|
<td><span class="badge bg-danger">Required</span></td>
|
|
@else
|
|
<td><span class="badge bg-success">Optional</span></td>
|
|
@end
|
|
<td>${param.description}</td>
|
|
<td><code class="text-muted">${param.example}</code></td>
|
|
</tr>
|
|
@end
|
|
</tbody>
|
|
</table>
|
|
@end
|
|
|
|
<!-- Return Value -->
|
|
@if method.result.name.len > 0
|
|
<h6>Returns:</h6>
|
|
<div class="alert alert-light">
|
|
<strong>Type:</strong> <span
|
|
class="badge bg-info">${method.result.type_info}</span><br>
|
|
<strong>Description:</strong> ${method.result.description}<br>
|
|
@if method.result.example.len > 0
|
|
<strong>Example:</strong> <code
|
|
class="text-muted">${method.result.example}</code>
|
|
@end
|
|
</div>
|
|
@end
|
|
|
|
<!-- Examples -->
|
|
<div class="row mt-3 examples-row">
|
|
<div class="col-md-6">
|
|
<h6>Example Request:</h6>
|
|
<div class="code-block">
|
|
<button class="copy-button"
|
|
onclick="copyToClipboard('request-${method.name}', this)">
|
|
Copy
|
|
</button>
|
|
<pre id="request-${method.name}">${method.example_request}</pre>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6>Example Response:</h6>
|
|
<div class="code-block">
|
|
<button class="copy-button"
|
|
onclick="copyToClipboard('response-${method.name}', this)">
|
|
Copy
|
|
</button>
|
|
<pre id="response-${method.name}">${method.example_response}</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Curl Example -->
|
|
<div class="curl-section">
|
|
<h6>Curl Command:</h6>
|
|
<button class="copy-button"
|
|
onclick="copyToClipboard('curl-${method.name}', this)">
|
|
Copy
|
|
</button>
|
|
<pre class="curl-command" id="curl-${method.name}">${method.curl_example}</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@end
|
|
</div>
|
|
</div>
|
|
@end
|
|
</div>
|
|
</div>
|
|
@end
|
|
|
|
<!-- API Objects (if any) -->
|
|
@if spec.objects.len > 0
|
|
@for object in spec.objects
|
|
<div class="object-section" id="object-${object.name}">
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<h2>${object.name.title()}</h2>
|
|
@if object.description.len > 0
|
|
<p>${object.description}</p>
|
|
@end
|
|
|
|
<!-- Methods for this object -->
|
|
@for method in object.methods
|
|
<div class="card method-card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">${method.name}</h5>
|
|
<div class="method-endpoint">POST ${method.endpoint_url}</div>
|
|
</div>
|
|
<div class="card-body">
|
|
@if method.summary.len > 0
|
|
<p class="card-text"><strong>${method.summary}</strong></p>
|
|
@end
|
|
@if method.description.len > 0
|
|
<p class="card-text">${method.description}</p>
|
|
@end
|
|
|
|
<!-- Parameters -->
|
|
@if method.params.len > 0
|
|
<h6>Parameters:</h6>
|
|
<table class="table table-sm param-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Type</th>
|
|
<th>Required</th>
|
|
<th>Description</th>
|
|
<th>Example</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@for param in method.params
|
|
<tr>
|
|
<td><code>${param.name}</code></td>
|
|
<td><span class="badge bg-secondary">${param.type_info}</span></td>
|
|
@if param.required
|
|
<td><span class="badge bg-danger">Required</span></td>
|
|
@else
|
|
<td><span class="badge bg-success">Optional</span></td>
|
|
@end
|
|
<td>${param.description}</td>
|
|
<td><code class="text-muted">${param.example}</code></td>
|
|
</tr>
|
|
@end
|
|
</tbody>
|
|
</table>
|
|
@end
|
|
|
|
<!-- Return Value -->
|
|
@if method.result.name.len > 0
|
|
<h6>Returns:</h6>
|
|
<div class="alert alert-light">
|
|
<strong>Type:</strong> <span class="badge bg-info">${method.result.type_info}</span><br>
|
|
<strong>Description:</strong> ${method.result.description}<br>
|
|
@if method.result.example.len > 0
|
|
<strong>Example:</strong> <code class="text-muted">${method.result.example}</code>
|
|
@end
|
|
</div>
|
|
@end
|
|
|
|
<!-- Examples -->
|
|
<div class="row mt-3 examples-row">
|
|
<div class="col-md-6">
|
|
<h6>Example Request:</h6>
|
|
<div class="code-block">
|
|
<button class="copy-button"
|
|
onclick="copyToClipboard('request-obj-${method.name}', this)">
|
|
Copy
|
|
</button>
|
|
<pre id="request-obj-${method.name}">${method.example_request}</pre>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6>Example Response:</h6>
|
|
<div class="code-block">
|
|
<button class="copy-button"
|
|
onclick="copyToClipboard('response-obj-${method.name}', this)">
|
|
Copy
|
|
</button>
|
|
<pre id="response-obj-${method.name}">${method.example_response}</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Curl Example -->
|
|
<div class="curl-section">
|
|
<h6>Curl Command:</h6>
|
|
<button class="copy-button" onclick="copyToClipboard('curl-obj-${method.name}', this)">
|
|
Copy
|
|
</button>
|
|
<pre class="curl-command" id="curl-obj-${method.name}">${method.curl_example}</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@end
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@end
|
|
@end
|
|
|
|
</div>
|
|
|
|
@include 'partials/footer'
|
|
|
|
<!-- Bootstrap JS Bundle (includes Popper) -->
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
|
|
@include 'partials/theme_script'
|
|
|
|
<!-- Copy to Clipboard Functionality -->
|
|
<script>
|
|
function copyToClipboard(elementId, button) {
|
|
const element = document.getElementById(elementId);
|
|
const text = element.textContent;
|
|
|
|
// Use the modern Clipboard API
|
|
if (navigator.clipboard && window.isSecureContext) {
|
|
navigator.clipboard.writeText(text).then(() => {
|
|
showCopySuccess(button);
|
|
}).catch(err => {
|
|
console.error('Failed to copy: ', err);
|
|
fallbackCopyTextToClipboard(text, button);
|
|
});
|
|
} else {
|
|
// Fallback for older browsers
|
|
fallbackCopyTextToClipboard(text, button);
|
|
}
|
|
}
|
|
|
|
function fallbackCopyTextToClipboard(text, button) {
|
|
const textArea = document.createElement("textarea");
|
|
textArea.value = text;
|
|
textArea.style.top = "0";
|
|
textArea.style.left = "0";
|
|
textArea.style.position = "fixed";
|
|
|
|
document.body.appendChild(textArea);
|
|
textArea.focus();
|
|
textArea.select();
|
|
|
|
try {
|
|
const successful = document.execCommand('copy');
|
|
if (successful) {
|
|
showCopySuccess(button);
|
|
}
|
|
} catch (err) {
|
|
console.error('Fallback: Oops, unable to copy', err);
|
|
}
|
|
|
|
document.body.removeChild(textArea);
|
|
}
|
|
|
|
function showCopySuccess(button) {
|
|
const originalText = button.textContent;
|
|
button.textContent = 'Copied!';
|
|
button.classList.add('copied');
|
|
|
|
setTimeout(() => {
|
|
button.textContent = originalText;
|
|
button.classList.remove('copied');
|
|
}, 2000);
|
|
}
|
|
|
|
// Toggle TOC group sections
|
|
function toggleTocGroup(groupId) {
|
|
const group = document.getElementById(groupId);
|
|
const toggleIcon = document.getElementById('toc-toggle-' + groupId.replace('toc-group-methods-', ''));
|
|
|
|
if (group && toggleIcon) {
|
|
const bsCollapse = bootstrap.Collapse.getOrCreateInstance(group);
|
|
|
|
// Check current state and toggle icon immediately
|
|
if (group.classList.contains('show')) {
|
|
// Currently expanded, will collapse
|
|
toggleIcon.classList.add('collapsed');
|
|
} else {
|
|
// Currently collapsed, will expand
|
|
toggleIcon.classList.remove('collapsed');
|
|
}
|
|
|
|
// Toggle the collapse
|
|
bsCollapse.toggle();
|
|
}
|
|
}
|
|
|
|
// Toggle method group sections
|
|
function toggleMethodGroup(groupId) {
|
|
const group = document.getElementById(groupId);
|
|
const toggleIcon = document.getElementById('group-toggle-' + groupId.replace('group-', ''));
|
|
|
|
if (group && toggleIcon) {
|
|
const bsCollapse = bootstrap.Collapse.getOrCreateInstance(group);
|
|
|
|
// Check current state and toggle icon immediately
|
|
if (group.classList.contains('show')) {
|
|
// Currently expanded, will collapse
|
|
toggleIcon.classList.add('collapsed');
|
|
} else {
|
|
// Currently collapsed, will expand
|
|
toggleIcon.classList.remove('collapsed');
|
|
}
|
|
|
|
// Toggle the collapse
|
|
bsCollapse.toggle();
|
|
}
|
|
}
|
|
|
|
// Reusable toggle function for method cards
|
|
function toggleMethodCard(bodyId) {
|
|
const body = document.getElementById(bodyId);
|
|
const toggleIcon = document.getElementById('toggle-' + bodyId.replace('method-body-', ''));
|
|
|
|
if (body && toggleIcon) {
|
|
const bsCollapse = bootstrap.Collapse.getOrCreateInstance(body);
|
|
|
|
// Check current state and toggle icon immediately
|
|
if (body.classList.contains('show')) {
|
|
// Currently expanded, will collapse
|
|
toggleIcon.classList.add('collapsed');
|
|
} else {
|
|
// Currently collapsed, will expand
|
|
toggleIcon.classList.remove('collapsed');
|
|
}
|
|
|
|
// Toggle the collapse
|
|
bsCollapse.toggle();
|
|
}
|
|
}
|
|
|
|
// Toggle TOC methods list
|
|
function toggleTocMethods() {
|
|
const methodItems = document.querySelectorAll('.toc-method-item');
|
|
const showMoreLink = document.getElementById('tocShowMore');
|
|
const isExpanded = showMoreLink.textContent.includes('Show less');
|
|
|
|
methodItems.forEach((item, index) => {
|
|
if (index > 0) {
|
|
item.style.display = isExpanded ? 'none' : 'list-item';
|
|
}
|
|
});
|
|
|
|
const totalMethods = methodItems.length;
|
|
showMoreLink.textContent = isExpanded
|
|
? 'Show all ' + totalMethods + ' methods ▼'
|
|
: 'Show less ▲';
|
|
}
|
|
|
|
// JSON Prettification
|
|
function prettifyJSON(jsonString) {
|
|
try {
|
|
// Try to parse and prettify the JSON
|
|
const parsed = JSON.parse(jsonString);
|
|
return JSON.stringify(parsed, null, 3); // 3 spaces indentation
|
|
} catch (e) {
|
|
// If parsing fails, return original string
|
|
return jsonString;
|
|
}
|
|
}
|
|
|
|
function prettifyCurlJSON(curlCommand) {
|
|
try {
|
|
// Extract JSON from curl -d '...' part
|
|
const match = curlCommand.match(/-d\s+'([^']+)'/);
|
|
if (match && match[1]) {
|
|
const jsonStr = match[1];
|
|
const parsed = JSON.parse(jsonStr);
|
|
const prettified = JSON.stringify(parsed, null, 2); // 2 spaces for curl
|
|
|
|
// Replace the compact JSON with prettified version
|
|
// Escape single quotes in the prettified JSON
|
|
const escapedPrettified = prettified.replace(/'/g, "'\\''");
|
|
return curlCommand.replace(jsonStr, escapedPrettified);
|
|
}
|
|
} catch (e) {
|
|
// If parsing fails, return original
|
|
}
|
|
return curlCommand;
|
|
}
|
|
|
|
function prettifyAllJSON() {
|
|
// Prettify example requests and responses
|
|
document.querySelectorAll('.code-block pre').forEach(pre => {
|
|
const content = pre.textContent.trim();
|
|
if (content && (content.startsWith('{') || content.startsWith('['))) {
|
|
pre.textContent = prettifyJSON(content);
|
|
}
|
|
});
|
|
|
|
// Prettify parameter examples in tables
|
|
document.querySelectorAll('.param-table code.text-muted').forEach(code => {
|
|
const content = code.textContent.trim();
|
|
if (content && (content.startsWith('{') || content.startsWith('['))) {
|
|
code.textContent = prettifyJSON(content);
|
|
}
|
|
});
|
|
|
|
// Prettify result examples
|
|
document.querySelectorAll('.alert-light code.text-muted').forEach(code => {
|
|
const content = code.textContent.trim();
|
|
if (content && (content.startsWith('{') || content.startsWith('['))) {
|
|
code.textContent = prettifyJSON(content);
|
|
}
|
|
});
|
|
|
|
// Prettify JSON in curl commands
|
|
document.querySelectorAll('.curl-command').forEach(pre => {
|
|
const content = pre.textContent.trim();
|
|
if (content.includes('curl') && content.includes('-d')) {
|
|
pre.textContent = prettifyCurlJSON(content);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Scroll to method and expand it
|
|
function scrollToMethod(methodName) {
|
|
// Find the method card
|
|
const methodCard = document.getElementById('method-' + methodName);
|
|
if (!methodCard) return;
|
|
|
|
// Find which group this method belongs to
|
|
const methodGroup = methodCard.closest('.method-group-content');
|
|
if (methodGroup) {
|
|
const groupId = methodGroup.id;
|
|
const groupIdx = groupId.replace('group-', '');
|
|
|
|
// Expand the group if collapsed
|
|
const bsGroupCollapse = bootstrap.Collapse.getOrCreateInstance(methodGroup);
|
|
if (!methodGroup.classList.contains('show')) {
|
|
bsGroupCollapse.show();
|
|
const groupToggle = document.getElementById('group-toggle-' + groupIdx);
|
|
if (groupToggle) {
|
|
groupToggle.classList.remove('collapsed');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find the method body
|
|
const methodBody = methodCard.querySelector('[id^="method-body-"]');
|
|
if (methodBody) {
|
|
// Expand the method if collapsed
|
|
const bsMethodCollapse = bootstrap.Collapse.getOrCreateInstance(methodBody);
|
|
if (!methodBody.classList.contains('show')) {
|
|
bsMethodCollapse.show();
|
|
const bodyId = methodBody.id;
|
|
const toggleIcon = document.getElementById('toggle-' + bodyId.replace('method-body-', ''));
|
|
if (toggleIcon) {
|
|
toggleIcon.classList.remove('collapsed');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Scroll to the method with smooth animation
|
|
setTimeout(() => {
|
|
methodCard.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
}, 300); // Wait for collapse animations to complete
|
|
}
|
|
|
|
// Handle hash navigation on page load and hash change
|
|
function handleHashNavigation() {
|
|
const hash = window.location.hash;
|
|
if (hash && hash.startsWith('#method-')) {
|
|
const methodName = hash.replace('#method-', '');
|
|
scrollToMethod(methodName);
|
|
}
|
|
}
|
|
|
|
// Table of Contents toggle functionality
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
// Prettify all JSON on page load
|
|
prettifyAllJSON();
|
|
|
|
// Handle hash changes (when clicking TOC links)
|
|
window.addEventListener('hashchange', handleHashNavigation);
|
|
const tocContent = document.getElementById('tocContent');
|
|
const tocHeader = document.querySelector('.toc-header');
|
|
const tocIcon = document.querySelector('.toc-toggle-icon');
|
|
|
|
if (tocContent && tocHeader && tocIcon) {
|
|
// Initialize Bootstrap Collapse
|
|
const bsCollapse = new bootstrap.Collapse(tocContent, {
|
|
toggle: false
|
|
});
|
|
|
|
// Handle click on header
|
|
tocHeader.addEventListener('click', function () {
|
|
bsCollapse.toggle();
|
|
});
|
|
|
|
// Handle icon rotation on collapse events
|
|
tocContent.addEventListener('show.bs.collapse', function () {
|
|
tocIcon.classList.remove('collapsed');
|
|
tocHeader.setAttribute('aria-expanded', 'true');
|
|
});
|
|
|
|
tocContent.addEventListener('hide.bs.collapse', function () {
|
|
tocIcon.classList.add('collapsed');
|
|
tocHeader.setAttribute('aria-expanded', 'false');
|
|
});
|
|
}
|
|
|
|
// Initialize TOC groups - only first group expanded by default
|
|
const tocGroups = document.querySelectorAll('[id^="toc-group-methods-"]');
|
|
tocGroups.forEach((group, index) => {
|
|
const groupId = group.id;
|
|
const toggleIcon = document.getElementById('toc-toggle-' + groupId.replace('toc-group-methods-', ''));
|
|
|
|
if (index === 0) {
|
|
// First group - keep it expanded
|
|
if (toggleIcon) {
|
|
toggleIcon.classList.remove('collapsed');
|
|
}
|
|
} else {
|
|
// Other groups - collapse them
|
|
const bsCollapse = new bootstrap.Collapse(group, { toggle: false });
|
|
bsCollapse.hide();
|
|
if (toggleIcon) {
|
|
toggleIcon.classList.add('collapsed');
|
|
}
|
|
}
|
|
});
|
|
|
|
// Initialize method groups - only first group expanded by default
|
|
const methodGroups = document.querySelectorAll('[id^="group-"]');
|
|
methodGroups.forEach((group, index) => {
|
|
const groupId = group.id;
|
|
const toggleIcon = document.getElementById('group-toggle-' + groupId.replace('group-', ''));
|
|
|
|
if (index === 0) {
|
|
// First group - keep it expanded
|
|
if (toggleIcon) {
|
|
toggleIcon.classList.remove('collapsed');
|
|
}
|
|
} else {
|
|
// Other groups - collapse them
|
|
const bsCollapse = new bootstrap.Collapse(group, { toggle: false });
|
|
bsCollapse.hide();
|
|
if (toggleIcon) {
|
|
toggleIcon.classList.add('collapsed');
|
|
}
|
|
}
|
|
});
|
|
|
|
// Initialize method cards - expand only the first method in the first group
|
|
// BUT: if there's a hash in the URL, don't expand the first method (hash navigation will handle it)
|
|
const hasHash = window.location.hash && window.location.hash.startsWith('#method-');
|
|
const methodBodies = document.querySelectorAll('[id^="method-body-"]');
|
|
methodBodies.forEach((body) => {
|
|
const bodyId = body.id;
|
|
const parts = bodyId.replace('method-body-', '').split('-');
|
|
const groupIdx = parts[0];
|
|
const methodIdx = parts[1];
|
|
const toggleIcon = document.getElementById('toggle-' + groupIdx + '-' + methodIdx);
|
|
|
|
if (!hasHash && groupIdx === '0' && methodIdx === '0') {
|
|
// First method in first group - expand it (only if no hash)
|
|
const bsCollapse = new bootstrap.Collapse(body, { toggle: false });
|
|
bsCollapse.show();
|
|
if (toggleIcon) {
|
|
toggleIcon.classList.remove('collapsed');
|
|
}
|
|
} else {
|
|
// All other methods - collapse them
|
|
if (toggleIcon) {
|
|
toggleIcon.classList.add('collapsed');
|
|
}
|
|
}
|
|
});
|
|
|
|
// Handle initial hash navigation AFTER all initialization is complete
|
|
// This ensures the hash navigation can properly expand the target method
|
|
if (hasHash) {
|
|
// Use setTimeout to ensure all Bootstrap collapse instances are initialized
|
|
setTimeout(() => {
|
|
handleHashNavigation();
|
|
}, 100);
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
|
|
</html> |