409 lines
13 KiB
HTML
409 lines
13 KiB
HTML
{{/*
|
|
RapiDoc API Documentation Renderer
|
|
|
|
Primary API documentation renderer using RapiDoc with "Mix your own HTML" slots.
|
|
See: https://rapidocweb.com/examples.html
|
|
|
|
Required page params:
|
|
- staticFilePath: Path to the OpenAPI specification file
|
|
|
|
Optional page params:
|
|
- operationId: Specific operation to display (renders only that operation)
|
|
- tag: Tag to filter operations by
|
|
|
|
RapiDoc slots available for custom content:
|
|
- slot="header" - Custom header
|
|
- slot="footer" - Custom footer
|
|
- slot="overview" - Custom overview content
|
|
- slot="auth" - Custom authentication section
|
|
- slot="nav-logo" - Custom navigation logo
|
|
*/}}
|
|
|
|
{{ $specPath := .Params.staticFilePath }}
|
|
{{ $specPathJSON := replace $specPath ".yaml" ".json" | replace ".yml" ".json" }}
|
|
{{ $operationId := .Params.operationId | default "" }}
|
|
{{ $tag := .Params.tag | default "" }}
|
|
|
|
{{/* Machine-readable links for AI agent discovery */}}
|
|
{{ if $specPath }}
|
|
<link rel="alternate" type="application/x-yaml" href="{{ $specPath | absURL }}" title="OpenAPI Specification (YAML)" />
|
|
<link rel="alternate" type="application/json" href="{{ $specPathJSON | absURL }}" title="OpenAPI Specification (JSON)" />
|
|
{{ end }}
|
|
|
|
<div class="api-reference-wrapper">
|
|
{{/* RapiDoc component with slot-based customization */}}
|
|
<rapi-doc
|
|
id="api-doc"
|
|
spec-url="{{ $specPath }}"
|
|
theme="light"
|
|
bg-color="#ffffff"
|
|
text-color="#2b2b2b"
|
|
header-color="#020a47"
|
|
primary-color="#00A3FF"
|
|
nav-bg-color="#f7f8fa"
|
|
nav-text-color="#2b2b2b"
|
|
nav-hover-bg-color="#e8e8f0"
|
|
nav-hover-text-color="#020a47"
|
|
nav-accent-color="#020a47"
|
|
regular-font="Proxima Nova, -apple-system, BlinkMacSystemFont, sans-serif"
|
|
mono-font="IBM Plex Mono, Monaco, Consolas, monospace"
|
|
font-size="large"
|
|
render-style="view"
|
|
layout="column"
|
|
schema-style="table"
|
|
default-schema-tab="model"
|
|
response-area-height="400px"
|
|
show-header="false"
|
|
show-info="false"
|
|
show-side-nav="false"
|
|
show-components="false"
|
|
allow-authentication="true"
|
|
allow-try="false"
|
|
allow-spec-url-load="false"
|
|
allow-spec-file-load="false"
|
|
allow-server-selection="false"
|
|
allow-search="false"
|
|
fill-request-fields-with-example="true"
|
|
persist-auth="false"
|
|
{{ if $operationId }}goto-path="op/{{ $operationId }}"{{ end }}
|
|
{{ if $tag }}match-paths="tag/{{ $tag }}"{{ end }}
|
|
>
|
|
{{/* Custom overview slot - Hugo page content */}}
|
|
{{ with .Content }}
|
|
<div slot="overview">
|
|
{{ . }}
|
|
</div>
|
|
{{ end }}
|
|
|
|
{{/* Custom examples from frontmatter */}}
|
|
{{ with .Params.examples }}
|
|
<div slot="footer" class="api-custom-examples">
|
|
<h3>Examples</h3>
|
|
{{ range . }}
|
|
<div class="api-example">
|
|
<h4>{{ .title }}</h4>
|
|
{{ with .description }}<p>{{ . | markdownify }}</p>{{ end }}
|
|
<pre><code class="language-{{ .lang | default "bash" }}">{{ .code }}</code></pre>
|
|
</div>
|
|
{{ end }}
|
|
</div>
|
|
{{ end }}
|
|
</rapi-doc>
|
|
</div>
|
|
|
|
{{/* Load RapiDoc from CDN */}}
|
|
<script type="module" src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"></script>
|
|
|
|
<script>
|
|
(function() {
|
|
'use strict';
|
|
|
|
console.log('[RapiDoc] Script loaded');
|
|
|
|
// Detect current theme by checking which theme stylesheet is enabled
|
|
function isDarkTheme() {
|
|
// Check for enabled dark-theme stylesheet
|
|
const darkStylesheet = document.querySelector('link[rel*="stylesheet"][title="dark-theme"]:not([disabled])');
|
|
if (darkStylesheet && !darkStylesheet.disabled) {
|
|
return true;
|
|
}
|
|
|
|
// Fallback: check for data-theme attribute (some pages may use this)
|
|
if (document.documentElement.dataset.theme === 'dark') {
|
|
return true;
|
|
}
|
|
|
|
// Fallback: check localStorage preference
|
|
try {
|
|
const stored = localStorage.getItem('defined_style_preference');
|
|
if (stored) {
|
|
const prefs = JSON.parse(stored);
|
|
return prefs.theme === 'dark';
|
|
}
|
|
} catch (e) {
|
|
// Ignore localStorage errors
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Update RapiDoc theme based on document theme
|
|
function updateRapiDocTheme() {
|
|
const rapiDoc = document.getElementById('api-doc');
|
|
if (!rapiDoc) return;
|
|
|
|
const isDark = isDarkTheme();
|
|
|
|
if (isDark) {
|
|
// Match Hugo dark theme: $grey10: #14141F
|
|
rapiDoc.setAttribute('theme', 'dark');
|
|
rapiDoc.setAttribute('bg-color', '#14141F');
|
|
rapiDoc.setAttribute('text-color', '#D4D7DD');
|
|
rapiDoc.setAttribute('header-color', '#D4D7DD');
|
|
rapiDoc.setAttribute('primary-color', '#a0a0ff');
|
|
rapiDoc.setAttribute('nav-bg-color', '#1a1a2a');
|
|
rapiDoc.setAttribute('nav-text-color', '#D4D7DD');
|
|
rapiDoc.setAttribute('nav-hover-bg-color', '#252535');
|
|
rapiDoc.setAttribute('nav-hover-text-color', '#ffffff');
|
|
rapiDoc.setAttribute('nav-accent-color', '#a0a0ff');
|
|
rapiDoc.setAttribute('code-theme', 'monokai');
|
|
} else {
|
|
// Match Hugo light theme: $g20-white: #FFFFFF
|
|
rapiDoc.setAttribute('theme', 'light');
|
|
rapiDoc.setAttribute('bg-color', '#ffffff');
|
|
rapiDoc.setAttribute('text-color', '#2b2b2b');
|
|
rapiDoc.setAttribute('header-color', '#020a47');
|
|
rapiDoc.setAttribute('primary-color', '#020a47');
|
|
rapiDoc.setAttribute('nav-bg-color', '#f7f8fa');
|
|
rapiDoc.setAttribute('nav-text-color', '#2b2b2b');
|
|
rapiDoc.setAttribute('nav-hover-bg-color', '#e8e8f0');
|
|
rapiDoc.setAttribute('nav-hover-text-color', '#020a47');
|
|
rapiDoc.setAttribute('nav-accent-color', '#020a47');
|
|
rapiDoc.setAttribute('code-theme', 'prism');
|
|
}
|
|
}
|
|
|
|
// Initialize when DOM is ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', updateRapiDocTheme);
|
|
} else {
|
|
updateRapiDocTheme();
|
|
}
|
|
|
|
// Set custom CSS properties for input border styling
|
|
function setInputBorderStyles() {
|
|
const rapiDoc = document.getElementById('api-doc');
|
|
if (!rapiDoc) {
|
|
console.log('[RapiDoc] Element not found');
|
|
return;
|
|
}
|
|
|
|
// Set CSS custom property on the rapi-doc element
|
|
// This will penetrate the shadow DOM
|
|
rapiDoc.style.setProperty('--border-color', '#00A3FF');
|
|
console.log('[RapiDoc] Set --border-color to #00A3FF');
|
|
}
|
|
|
|
// Wait for RapiDoc custom element to be fully defined
|
|
if (customElements && customElements.whenDefined) {
|
|
customElements.whenDefined('rapi-doc').then(() => {
|
|
console.log('[RapiDoc] Custom element defined');
|
|
setInputBorderStyles();
|
|
// Set again after render
|
|
setTimeout(setInputBorderStyles, 500);
|
|
});
|
|
} else {
|
|
// Fallback for older browsers
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', setInputBorderStyles);
|
|
} else {
|
|
setInputBorderStyles();
|
|
}
|
|
setTimeout(setInputBorderStyles, 500);
|
|
}
|
|
|
|
// Watch for stylesheet changes (theme toggles enable/disable stylesheets)
|
|
const observer = new MutationObserver(function(mutations) {
|
|
mutations.forEach(function(mutation) {
|
|
// Check if a stylesheet's disabled attribute changed
|
|
if (mutation.type === 'attributes' &&
|
|
mutation.target.tagName === 'LINK' &&
|
|
mutation.target.title &&
|
|
mutation.target.title.includes('theme')) {
|
|
updateRapiDocTheme();
|
|
}
|
|
// Also watch for data-theme changes as a fallback
|
|
if (mutation.attributeName === 'data-theme') {
|
|
updateRapiDocTheme();
|
|
}
|
|
});
|
|
});
|
|
|
|
// Observe the head element for stylesheet changes
|
|
observer.observe(document.head, {
|
|
attributes: true,
|
|
attributeFilter: ['disabled'],
|
|
subtree: true
|
|
});
|
|
|
|
// Also observe the document element for data-theme changes
|
|
observer.observe(document.documentElement, {
|
|
attributes: true,
|
|
attributeFilter: ['data-theme']
|
|
});
|
|
|
|
// Hide "Expand all | Collapse all" controls and Overview section inside shadow DOM
|
|
function hideExpandCollapseControls() {
|
|
const rapiDoc = document.getElementById('api-doc');
|
|
if (!rapiDoc) return;
|
|
|
|
// Wait for RapiDoc to render, try multiple times
|
|
let attempts = 0;
|
|
const maxAttempts = 10;
|
|
|
|
const tryHide = function() {
|
|
attempts++;
|
|
try {
|
|
const shadowRoot = rapiDoc.shadowRoot;
|
|
if (!shadowRoot) {
|
|
if (attempts < maxAttempts) {
|
|
setTimeout(tryHide, 500);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Find all elements in shadow DOM and hide those containing "Expand all"
|
|
const allElements = shadowRoot.querySelectorAll('*');
|
|
let hiddenCount = 0;
|
|
|
|
allElements.forEach(function(element) {
|
|
const text = element.textContent || '';
|
|
|
|
// Check if element contains "Expand all", "Collapse all"
|
|
if (text.includes('Expand all') || text.includes('Collapse all')) {
|
|
// Hide the element and its parent containers
|
|
element.style.display = 'none';
|
|
if (element.parentElement) {
|
|
element.parentElement.style.display = 'none';
|
|
}
|
|
hiddenCount++;
|
|
}
|
|
});
|
|
|
|
// Separately target all headings and hide those with "Overview"
|
|
const headings = shadowRoot.querySelectorAll('h1, h2, h3, h4');
|
|
headings.forEach(function(heading) {
|
|
const text = (heading.textContent || '').trim();
|
|
const innerText = (heading.innerText || '').trim();
|
|
|
|
// Log for debugging
|
|
if (text.includes('Overview') || innerText.includes('Overview')) {
|
|
console.log('[RapiDoc] Found heading:', heading.tagName, 'with text:', text, 'innerText:', innerText);
|
|
heading.style.display = 'none';
|
|
hiddenCount++;
|
|
}
|
|
});
|
|
|
|
// Also inject CSS as backup to hide Overview sections
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
/* Hide Overview section and headings */
|
|
.section-gap.section-tag,
|
|
[id*="overview"],
|
|
.regular-font.section-gap:empty,
|
|
h1:empty, h2:empty, h3:empty {
|
|
display: none !important;
|
|
}
|
|
`;
|
|
shadowRoot.appendChild(style);
|
|
|
|
console.log('[RapiDoc] Hidden ' + hiddenCount + ' elements containing expand/collapse text (attempt ' + attempts + ')');
|
|
|
|
if (hiddenCount === 0 && attempts < maxAttempts) {
|
|
// Try again if nothing was hidden
|
|
setTimeout(tryHide, 500);
|
|
}
|
|
} catch (e) {
|
|
console.log('[RapiDoc] Could not access shadow DOM:', e);
|
|
if (attempts < maxAttempts) {
|
|
setTimeout(tryHide, 500);
|
|
}
|
|
}
|
|
};
|
|
|
|
setTimeout(tryHide, 500);
|
|
}
|
|
|
|
// Call after RapiDoc initializes
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', hideExpandCollapseControls);
|
|
} else {
|
|
hideExpandCollapseControls();
|
|
}
|
|
})();
|
|
</script>
|
|
|
|
<style>
|
|
.api-reference-wrapper {
|
|
width: 100%;
|
|
}
|
|
|
|
rapi-doc {
|
|
width: 100%;
|
|
min-height: 600px;
|
|
display: block;
|
|
/* Override RapiDoc's internal font sizes to match Hugo docs */
|
|
--font-size-small: 15px;
|
|
--font-size-mono: 15px;
|
|
--font-size-regular: 17px;
|
|
/* Match Hugo theme backgrounds - light mode default */
|
|
--bg: #ffffff;
|
|
--bg2: #f7f8fa;
|
|
--bg3: #eef0f3;
|
|
/* Input field border styling - subtle with transparency */
|
|
--border-color: rgba(0, 163, 255, 0.25);
|
|
--light-border-color: rgba(0, 163, 255, 0.15);
|
|
/* HTTP method colors - lighter palette for light theme */
|
|
--blue: #00A3FF; /* $b-pool - GET */
|
|
--green: #34BB55; /* $gr-rainforest - POST */
|
|
--orange: #FFB94A; /* $y-pineapple - PUT (distinct from red) */
|
|
--red: #F95F53; /* $r-curacao - DELETE */
|
|
--purple: #9b2aff; /* $br-new-purple - PATCH */
|
|
}
|
|
|
|
/* Dark mode overrides - match Hugo $grey10: #14141F */
|
|
[data-theme="dark"] rapi-doc,
|
|
html:has(link[title="dark-theme"]:not([disabled])) rapi-doc {
|
|
--bg: #14141F;
|
|
/* Subtle border colors for dark mode with transparency */
|
|
--border-color: rgba(0, 163, 255, 0.35);
|
|
--light-border-color: rgba(0, 163, 255, 0.2);
|
|
--bg2: #1a1a2a;
|
|
--bg3: #252535;
|
|
--fg: #D4D7DD;
|
|
--fg2: #c8ccd2;
|
|
--fg3: #b0b4ba;
|
|
/* HTTP method colors - darker palette for dark theme */
|
|
--blue: #066FC5; /* $b-ocean - GET */
|
|
--green: #009F5F; /* $gr-viridian - POST */
|
|
--orange: #FFC800; /* $y-thunder - PUT (brighter for dark mode) */
|
|
--red: #DC4E58; /* $r-fire - DELETE */
|
|
--purple: #8E1FC3; /* $p-amethyst - PATCH */
|
|
}
|
|
|
|
/* Custom examples section styling */
|
|
.api-custom-examples {
|
|
padding: 1.5rem;
|
|
background: var(--bg2, #f7f8fa);
|
|
border-radius: 4px;
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.api-custom-examples h3 {
|
|
margin-top: 0;
|
|
margin-bottom: 1rem;
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
.api-example {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.api-example:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.api-example h4 {
|
|
margin: 0 0 0.5rem 0;
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.api-example pre {
|
|
margin: 0;
|
|
padding: 1rem;
|
|
background: var(--bg3, #eef0f3);
|
|
border-radius: 4px;
|
|
overflow-x: auto;
|
|
}
|
|
</style>
|