refactor(api): Remove Scalar renderer and related code
Remove unused Scalar API documentation renderer: - Delete layouts/partials/api/scalar.html - Delete assets/js/components/api-scalar.ts - Simplify renderer.html to only use RapiDoc - Remove ApiScalar from main.js component registry - Remove apiRenderer config option from hugo.ymlclaude/api-code-samples-plan-MEkQO
parent
f882370083
commit
e2939dc715
|
|
@ -1,326 +0,0 @@
|
|||
/**
|
||||
* Scalar API Documentation Component
|
||||
*
|
||||
* Initializes the Scalar API reference viewer for OpenAPI documentation.
|
||||
* Features:
|
||||
* - Dynamic CDN loading of Scalar library
|
||||
* - Theme synchronization with site theme
|
||||
* - InfluxData brand colors
|
||||
* - Error handling and fallback UI
|
||||
*
|
||||
* Usage:
|
||||
* <div data-component="api-scalar" data-spec-path="/path/to/spec.yml"></div>
|
||||
*/
|
||||
|
||||
import { getPreference } from '../services/local-storage.js';
|
||||
|
||||
interface ComponentOptions {
|
||||
component: HTMLElement;
|
||||
}
|
||||
|
||||
interface ScalarConfig {
|
||||
url: string;
|
||||
forceDarkModeState?: 'dark' | 'light';
|
||||
layout?: 'classic' | 'modern';
|
||||
showSidebar?: boolean;
|
||||
hideDarkModeToggle?: boolean;
|
||||
hideSearch?: boolean;
|
||||
documentDownloadType?: 'none' | 'yaml' | 'json';
|
||||
hideModels?: boolean;
|
||||
hideTestRequestButton?: boolean;
|
||||
withDefaultFonts?: boolean;
|
||||
customCss?: string;
|
||||
}
|
||||
|
||||
type ScalarCreateFn = (
|
||||
selector: string | HTMLElement,
|
||||
config: ScalarConfig
|
||||
) => void;
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
Scalar?: {
|
||||
createApiReference: ScalarCreateFn;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const SCALAR_CDN = 'https://cdn.jsdelivr.net/npm/@scalar/api-reference@latest';
|
||||
|
||||
/**
|
||||
* Load script dynamically
|
||||
*/
|
||||
function loadScript(src: string, timeout = 8000): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Check if script already exists
|
||||
const existing = Array.from(document.scripts).find(
|
||||
(s) => s.src && s.src.includes(src)
|
||||
);
|
||||
if (existing && window.Scalar?.createApiReference) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.src = src;
|
||||
script.defer = true;
|
||||
script.onload = () => resolve();
|
||||
script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
|
||||
|
||||
document.head.appendChild(script);
|
||||
|
||||
// Fallback timeout
|
||||
setTimeout(() => {
|
||||
if (window.Scalar?.createApiReference) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`Timeout loading script: ${src}`));
|
||||
}
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current theme from localStorage (source of truth for Hugo theme system)
|
||||
*/
|
||||
function getTheme(): 'dark' | 'light' {
|
||||
const theme = getPreference('theme');
|
||||
return theme === 'dark' ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll for Scalar availability
|
||||
*/
|
||||
function waitForScalar(maxAttempts = 50, interval = 100): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let attempts = 0;
|
||||
|
||||
const checkInterval = setInterval(() => {
|
||||
attempts++;
|
||||
|
||||
if (window.Scalar?.createApiReference) {
|
||||
clearInterval(checkInterval);
|
||||
resolve();
|
||||
} else if (attempts >= maxAttempts) {
|
||||
clearInterval(checkInterval);
|
||||
reject(
|
||||
new Error(`Scalar not available after ${maxAttempts * interval}ms`)
|
||||
);
|
||||
}
|
||||
}, interval);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Scalar API reference
|
||||
*/
|
||||
async function initScalar(
|
||||
container: HTMLElement,
|
||||
specUrl: string
|
||||
): Promise<void> {
|
||||
if (!window.Scalar?.createApiReference) {
|
||||
throw new Error('Scalar is not available');
|
||||
}
|
||||
|
||||
// Clean up previous Scalar instance (important for theme switching)
|
||||
// Remove any Scalar-injected content and classes
|
||||
container.innerHTML = '';
|
||||
// Remove Scalar's dark-mode class from body if it exists
|
||||
document.body.classList.remove('dark-mode');
|
||||
|
||||
const isDark = getTheme() === 'dark';
|
||||
|
||||
window.Scalar.createApiReference(container, {
|
||||
url: specUrl,
|
||||
forceDarkModeState: getTheme(),
|
||||
layout: 'classic',
|
||||
showSidebar: false,
|
||||
hideDarkModeToggle: true,
|
||||
hideSearch: true,
|
||||
documentDownloadType: 'none',
|
||||
hideModels: false,
|
||||
hideTestRequestButton: false,
|
||||
withDefaultFonts: false,
|
||||
customCss: `
|
||||
:root {
|
||||
/* Typography - match Hugo docs site */
|
||||
--scalar-font: 'Proxima Nova', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
--scalar-font-code: 'IBM Plex Mono', Monaco, Consolas, monospace;
|
||||
--scalar-font-size-base: 16px;
|
||||
--scalar-line-height: 1.65;
|
||||
|
||||
/* InfluxData brand colors */
|
||||
--scalar-color-1: #F63C41;
|
||||
--scalar-color-2: #d32f34;
|
||||
--scalar-color-accent: #F63C41;
|
||||
|
||||
/* Border radius */
|
||||
--scalar-radius: 4px;
|
||||
--scalar-radius-lg: 8px;
|
||||
|
||||
/* Background and text colors - theme-aware */
|
||||
--scalar-background-1: ${isDark ? '#1a1a2e' : '#ffffff'};
|
||||
--scalar-background-2: ${isDark ? '#232338' : '#f7f8fa'};
|
||||
--scalar-background-3: ${isDark ? '#2d2d44' : '#f0f2f5'};
|
||||
--scalar-text-1: ${isDark ? '#e0e0e0' : '#2b2b2b'};
|
||||
--scalar-text-2: ${isDark ? '#a0a0a0' : '#545454'};
|
||||
--scalar-text-3: ${isDark ? '#888888' : '#757575'};
|
||||
--scalar-border-color: ${isDark ? '#3a3a50' : '#e0e0e0'};
|
||||
|
||||
/* Heading colors */
|
||||
--scalar-heading-color: ${isDark ? '#ffffff' : '#2b2b2b'};
|
||||
}
|
||||
|
||||
/* Match Hugo heading styles */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: var(--scalar-font);
|
||||
font-weight: 600;
|
||||
color: var(--scalar-heading-color);
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
h1 { font-size: 2rem; }
|
||||
h2 { font-size: 1.5rem; margin-top: 2rem; }
|
||||
h3 { font-size: 1.25rem; margin-top: 1.5rem; }
|
||||
h4 { font-size: 1rem; margin-top: 1rem; }
|
||||
|
||||
/* Body text size */
|
||||
p, li, td, th {
|
||||
font-size: 1rem;
|
||||
line-height: var(--scalar-line-height);
|
||||
}
|
||||
|
||||
/* Code block styling */
|
||||
pre, code {
|
||||
font-family: var(--scalar-font-code);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
/* Hide section-content div */
|
||||
div.section-content {
|
||||
display: none !important;
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
console.log(
|
||||
'[API Docs] Scalar initialized with spec:',
|
||||
specUrl,
|
||||
'theme:',
|
||||
getTheme()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show error message in container
|
||||
*/
|
||||
function showError(container: HTMLElement, message: string): void {
|
||||
container.innerHTML = `<p class="error">${message}</p>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch for Hugo theme changes via stylesheet manipulation
|
||||
* Hugo theme.js enables/disables link[title*="theme"] elements
|
||||
*/
|
||||
function watchThemeChanges(container: HTMLElement, specUrl: string): void {
|
||||
// Watch for stylesheet changes in the document
|
||||
const observer = new MutationObserver(() => {
|
||||
const currentTheme = getTheme();
|
||||
console.log('[API Docs] Theme changed to:', currentTheme);
|
||||
// Re-initialize Scalar with new theme
|
||||
initScalar(container, specUrl).catch((error) => {
|
||||
console.error(
|
||||
'[API Docs] Failed to re-initialize Scalar on theme change:',
|
||||
error
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Watch for changes to stylesheet link elements
|
||||
const head = document.querySelector('head');
|
||||
if (head) {
|
||||
observer.observe(head, {
|
||||
attributes: true,
|
||||
attributeFilter: ['disabled'],
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Also watch for localStorage changes from other tabs
|
||||
window.addEventListener('storage', (event) => {
|
||||
if (event.key === 'influxdata_docs_preferences' && event.newValue) {
|
||||
try {
|
||||
const prefs = JSON.parse(event.newValue);
|
||||
if (prefs.theme) {
|
||||
const currentTheme = getTheme();
|
||||
console.log(
|
||||
'[API Docs] Theme changed via storage event to:',
|
||||
currentTheme
|
||||
);
|
||||
initScalar(container, specUrl).catch((error) => {
|
||||
console.error(
|
||||
'[API Docs] Failed to re-initialize Scalar on storage change:',
|
||||
error
|
||||
);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'[API Docs] Failed to parse localStorage preferences:',
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize API Scalar component
|
||||
*/
|
||||
export default async function ApiScalar({
|
||||
component,
|
||||
}: ComponentOptions): Promise<void> {
|
||||
try {
|
||||
// Get spec path from data attribute
|
||||
const specPath = component.dataset.specPath;
|
||||
const cdn = component.dataset.cdn || SCALAR_CDN;
|
||||
|
||||
if (!specPath) {
|
||||
console.error('[API Docs] No OpenAPI specification path provided');
|
||||
showError(
|
||||
component,
|
||||
'Error: No API specification configured for this page.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build full URL for spec (Scalar needs absolute URL)
|
||||
const specUrl = window.location.origin + specPath;
|
||||
|
||||
// Load Scalar from CDN if not already loaded
|
||||
if (!window.Scalar?.createApiReference) {
|
||||
try {
|
||||
await loadScript(cdn);
|
||||
} catch (err) {
|
||||
console.error('[API Docs] Failed to load Scalar from CDN', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for Scalar to be ready
|
||||
try {
|
||||
await waitForScalar();
|
||||
} catch (err) {
|
||||
console.error('[API Docs] Scalar failed to initialize', err);
|
||||
showError(component, 'Error: API viewer failed to load.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize Scalar
|
||||
await initScalar(component, specUrl);
|
||||
|
||||
// Watch for theme changes and re-initialize Scalar when theme changes
|
||||
watchThemeChanges(component, specUrl);
|
||||
} catch (err) {
|
||||
console.error('[API Docs] ApiScalar component error', err);
|
||||
showError(component, 'Error: API viewer failed to initialize.');
|
||||
}
|
||||
}
|
||||
|
|
@ -47,7 +47,6 @@ import { SidebarToggle } from './sidebar-toggle.js';
|
|||
import Theme from './theme.js';
|
||||
import ThemeSwitch from './theme-switch.js';
|
||||
import ApiRapiDoc from './components/api-rapidoc.ts';
|
||||
import ApiScalar from './components/api-scalar.ts';
|
||||
import ApiTabs from './components/api-tabs.ts';
|
||||
import ApiToc from './components/api-toc.ts';
|
||||
import RapiDocMini from './components/rapidoc-mini.ts';
|
||||
|
|
@ -83,7 +82,6 @@ const componentRegistry = {
|
|||
theme: Theme,
|
||||
'theme-switch': ThemeSwitch,
|
||||
'api-rapidoc': ApiRapiDoc,
|
||||
'api-scalar': ApiScalar,
|
||||
'api-tabs': ApiTabs,
|
||||
'api-toc': ApiToc,
|
||||
'rapidoc-mini': RapiDocMini,
|
||||
|
|
|
|||
|
|
@ -98,8 +98,6 @@ module:
|
|||
params:
|
||||
env: development
|
||||
environment: development
|
||||
# API documentation renderer: "scalar" (default) or "rapidoc"
|
||||
apiRenderer: rapidoc
|
||||
|
||||
# Configure the server for development
|
||||
server:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,779 @@
|
|||
# API Code Review Fixes Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Fix code review violations by extracting inline JavaScript from rapidoc.html into a TypeScript component and removing unused Scalar renderer code.
|
||||
|
||||
**Architecture:** Create a new `api-rapidoc.ts` TypeScript component following the established component pattern (same as `rapidoc-mini.ts`). The component handles theme synchronization, shadow DOM manipulation, and MutationObserver setup. Remove the Scalar renderer, api-tabs component, and associated partials since they're no longer used.
|
||||
|
||||
**Tech Stack:** TypeScript, Hugo templates, SCSS, Cypress
|
||||
|
||||
***
|
||||
|
||||
## Task 1: Create api-rapidoc.ts TypeScript Component
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `assets/js/components/api-rapidoc.ts`
|
||||
|
||||
**Step 1: Create the TypeScript component file**
|
||||
|
||||
Create `assets/js/components/api-rapidoc.ts` with the following content:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* RapiDoc API Documentation Component
|
||||
*
|
||||
* Initializes the full RapiDoc renderer with theme synchronization.
|
||||
* This is the component version of the inline JavaScript from rapidoc.html.
|
||||
*
|
||||
* Features:
|
||||
* - Theme detection from Hugo's stylesheet toggle system
|
||||
* - Automatic theme synchronization when user toggles dark/light mode
|
||||
* - Shadow DOM manipulation to hide unwanted UI elements
|
||||
* - CSS custom property injection for styling
|
||||
*
|
||||
* Usage:
|
||||
* <div data-component="api-rapidoc" data-spec-url="/path/to/spec.yml"></div>
|
||||
*
|
||||
* The component expects a <rapi-doc> element to already exist in the container
|
||||
* (created by Hugo template) or will wait for it to be added.
|
||||
*/
|
||||
|
||||
import { getPreference } from '../services/local-storage.js';
|
||||
|
||||
interface ComponentOptions {
|
||||
component: HTMLElement;
|
||||
}
|
||||
|
||||
interface ThemeColors {
|
||||
theme: 'light' | 'dark';
|
||||
bgColor: string;
|
||||
textColor: string;
|
||||
headerColor: string;
|
||||
primaryColor: string;
|
||||
navBgColor: string;
|
||||
navTextColor: string;
|
||||
navHoverBgColor: string;
|
||||
navHoverTextColor: string;
|
||||
navAccentColor: string;
|
||||
codeTheme: string;
|
||||
}
|
||||
|
||||
type CleanupFn = () => void;
|
||||
|
||||
/**
|
||||
* Get current theme from localStorage (source of truth for Hugo theme system)
|
||||
*/
|
||||
function getTheme(): 'dark' | 'light' {
|
||||
const theme = getPreference('theme');
|
||||
return theme === 'dark' ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get theme colors matching Hugo SCSS variables
|
||||
*/
|
||||
function getThemeColors(isDark: boolean): ThemeColors {
|
||||
if (isDark) {
|
||||
return {
|
||||
theme: 'dark',
|
||||
bgColor: '#14141F', // $grey10 ($article-bg in dark theme)
|
||||
textColor: '#D4D7DD', // $g15-platinum
|
||||
headerColor: '#D4D7DD',
|
||||
primaryColor: '#a0a0ff',
|
||||
navBgColor: '#1a1a2a',
|
||||
navTextColor: '#D4D7DD',
|
||||
navHoverBgColor: '#252535',
|
||||
navHoverTextColor: '#ffffff',
|
||||
navAccentColor: '#a0a0ff',
|
||||
codeTheme: 'monokai',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
theme: 'light',
|
||||
bgColor: '#ffffff', // $g20-white
|
||||
textColor: '#2b2b2b',
|
||||
headerColor: '#020a47', // $br-dark-blue
|
||||
primaryColor: '#020a47',
|
||||
navBgColor: '#f7f8fa',
|
||||
navTextColor: '#2b2b2b',
|
||||
navHoverBgColor: '#e8e8f0',
|
||||
navHoverTextColor: '#020a47',
|
||||
navAccentColor: '#020a47',
|
||||
codeTheme: 'prism',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply theme to RapiDoc element
|
||||
*/
|
||||
function applyTheme(rapiDoc: HTMLElement): void {
|
||||
const isDark = getTheme() === 'dark';
|
||||
const colors = getThemeColors(isDark);
|
||||
|
||||
rapiDoc.setAttribute('theme', colors.theme);
|
||||
rapiDoc.setAttribute('bg-color', colors.bgColor);
|
||||
rapiDoc.setAttribute('text-color', colors.textColor);
|
||||
rapiDoc.setAttribute('header-color', colors.headerColor);
|
||||
rapiDoc.setAttribute('primary-color', colors.primaryColor);
|
||||
rapiDoc.setAttribute('nav-bg-color', colors.navBgColor);
|
||||
rapiDoc.setAttribute('nav-text-color', colors.navTextColor);
|
||||
rapiDoc.setAttribute('nav-hover-bg-color', colors.navHoverBgColor);
|
||||
rapiDoc.setAttribute('nav-hover-text-color', colors.navHoverTextColor);
|
||||
rapiDoc.setAttribute('nav-accent-color', colors.navAccentColor);
|
||||
rapiDoc.setAttribute('code-theme', colors.codeTheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set custom CSS properties on RapiDoc element
|
||||
*/
|
||||
function setInputBorderStyles(rapiDoc: HTMLElement): void {
|
||||
rapiDoc.style.setProperty('--border-color', '#00A3FF');
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide unwanted elements in RapiDoc shadow DOM
|
||||
*/
|
||||
function hideExpandCollapseControls(rapiDoc: HTMLElement): void {
|
||||
const maxAttempts = 10;
|
||||
let attempts = 0;
|
||||
|
||||
const tryHide = (): void => {
|
||||
attempts++;
|
||||
|
||||
try {
|
||||
const shadowRoot = rapiDoc.shadowRoot;
|
||||
if (!shadowRoot) {
|
||||
if (attempts < maxAttempts) {
|
||||
setTimeout(tryHide, 500);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Find all elements and hide those containing "Expand all" / "Collapse all"
|
||||
const allElements = shadowRoot.querySelectorAll('*');
|
||||
let hiddenCount = 0;
|
||||
|
||||
allElements.forEach((element) => {
|
||||
const text = element.textContent || '';
|
||||
|
||||
if (text.includes('Expand all') || text.includes('Collapse all')) {
|
||||
(element as HTMLElement).style.display = 'none';
|
||||
if (element.parentElement) {
|
||||
element.parentElement.style.display = 'none';
|
||||
}
|
||||
hiddenCount++;
|
||||
}
|
||||
});
|
||||
|
||||
// Hide "Overview" headings
|
||||
const headings = shadowRoot.querySelectorAll('h1, h2, h3, h4');
|
||||
headings.forEach((heading) => {
|
||||
const text = (heading.textContent || '').trim();
|
||||
if (text.includes('Overview')) {
|
||||
(heading as HTMLElement).style.display = 'none';
|
||||
hiddenCount++;
|
||||
}
|
||||
});
|
||||
|
||||
// Inject CSS as backup
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.section-gap.section-tag,
|
||||
[id*="overview"],
|
||||
.regular-font.section-gap:empty,
|
||||
h1:empty, h2:empty, h3:empty {
|
||||
display: none !important;
|
||||
}
|
||||
`;
|
||||
shadowRoot.appendChild(style);
|
||||
|
||||
if (hiddenCount === 0 && attempts < maxAttempts) {
|
||||
setTimeout(tryHide, 500);
|
||||
}
|
||||
} catch (e) {
|
||||
if (attempts < maxAttempts) {
|
||||
setTimeout(tryHide, 500);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(tryHide, 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch for theme changes via stylesheet toggle
|
||||
*/
|
||||
function watchThemeChanges(rapiDoc: HTMLElement): CleanupFn {
|
||||
const handleThemeChange = (): void => {
|
||||
applyTheme(rapiDoc);
|
||||
};
|
||||
|
||||
// Watch stylesheet disabled attribute changes (Hugo theme.js toggles this)
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
for (const mutation of mutations) {
|
||||
if (
|
||||
mutation.type === 'attributes' &&
|
||||
mutation.target instanceof HTMLLinkElement &&
|
||||
mutation.target.title?.includes('theme')
|
||||
) {
|
||||
handleThemeChange();
|
||||
break;
|
||||
}
|
||||
// Also watch data-theme changes as fallback
|
||||
if (mutation.attributeName === 'data-theme') {
|
||||
handleThemeChange();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Observe head for stylesheet changes
|
||||
observer.observe(document.head, {
|
||||
attributes: true,
|
||||
attributeFilter: ['disabled'],
|
||||
subtree: true,
|
||||
});
|
||||
|
||||
// Observe documentElement for data-theme changes
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['data-theme'],
|
||||
});
|
||||
|
||||
return (): void => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize RapiDoc component
|
||||
*/
|
||||
export default function ApiRapiDoc({
|
||||
component,
|
||||
}: ComponentOptions): CleanupFn | void {
|
||||
// Find the rapi-doc element inside the container
|
||||
const rapiDoc = component.querySelector('rapi-doc') as HTMLElement | null;
|
||||
|
||||
if (!rapiDoc) {
|
||||
console.warn('[API RapiDoc] No rapi-doc element found in container');
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply initial theme
|
||||
applyTheme(rapiDoc);
|
||||
|
||||
// Set custom CSS properties
|
||||
if (customElements && customElements.whenDefined) {
|
||||
customElements.whenDefined('rapi-doc').then(() => {
|
||||
setInputBorderStyles(rapiDoc);
|
||||
setTimeout(() => setInputBorderStyles(rapiDoc), 500);
|
||||
});
|
||||
} else {
|
||||
setInputBorderStyles(rapiDoc);
|
||||
setTimeout(() => setInputBorderStyles(rapiDoc), 500);
|
||||
}
|
||||
|
||||
// Hide unwanted UI elements
|
||||
hideExpandCollapseControls(rapiDoc);
|
||||
|
||||
// Watch for theme changes
|
||||
return watchThemeChanges(rapiDoc);
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Verify the file was created correctly**
|
||||
|
||||
Run: `head -30 assets/js/components/api-rapidoc.ts`
|
||||
Expected: File header and imports visible
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add assets/js/components/api-rapidoc.ts
|
||||
git commit -m "feat(api): Create api-rapidoc TypeScript component
|
||||
|
||||
Extract inline JavaScript from rapidoc.html into a proper TypeScript
|
||||
component following the established component pattern."
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## Task 2: Register api-rapidoc Component in main.js
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `assets/js/main.js:49-88`
|
||||
|
||||
**Step 1: Add import for ApiRapiDoc**
|
||||
|
||||
Add this import after line 52 (after RapiDocMini import):
|
||||
|
||||
```javascript
|
||||
import ApiRapiDoc from './components/api-rapidoc.ts';
|
||||
```
|
||||
|
||||
**Step 2: Register component in componentRegistry**
|
||||
|
||||
Add this entry in the componentRegistry object (after line 87, the 'rapidoc-mini' entry):
|
||||
|
||||
```javascript
|
||||
'api-rapidoc': ApiRapiDoc,
|
||||
```
|
||||
|
||||
**Step 3: Verify changes**
|
||||
|
||||
Run: `grep -n "api-rapidoc\|ApiRapiDoc" assets/js/main.js`
|
||||
Expected: Both the import and registry entry appear
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add assets/js/main.js
|
||||
git commit -m "feat(api): Register api-rapidoc component in main.js"
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## Task 3: Update rapidoc.html to Use Component Pattern
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `layouts/partials/api/rapidoc.html`
|
||||
|
||||
**Step 1: Replace inline JavaScript with data-component attribute**
|
||||
|
||||
Replace the entire content of `layouts/partials/api/rapidoc.html` with:
|
||||
|
||||
```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" data-component="api-rapidoc">
|
||||
{{/* 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>
|
||||
|
||||
<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>
|
||||
```
|
||||
|
||||
**Step 2: Verify the inline script is removed**
|
||||
|
||||
Run: `grep -c "<script>" layouts/partials/api/rapidoc.html`
|
||||
Expected: `1` (only the CDN script tag, no inline JavaScript)
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add layouts/partials/api/rapidoc.html
|
||||
git commit -m "refactor(api): Replace inline JS with data-component in rapidoc.html
|
||||
|
||||
Remove ~230 lines of inline JavaScript and use the new api-rapidoc
|
||||
TypeScript component via data-component attribute instead."
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## Task 4: Remove Scalar Renderer and Related Code
|
||||
|
||||
**Files:**
|
||||
|
||||
- Delete: `layouts/partials/api/scalar.html`
|
||||
- Delete: `assets/js/components/api-scalar.ts`
|
||||
- Modify: `layouts/partials/api/renderer.html`
|
||||
- Modify: `assets/js/main.js`
|
||||
- Modify: `config/_default/hugo.yml`
|
||||
|
||||
**Step 1: Delete scalar.html partial**
|
||||
|
||||
Run: `rm layouts/partials/api/scalar.html`
|
||||
|
||||
**Step 2: Delete api-scalar.ts component**
|
||||
|
||||
Run: `rm assets/js/components/api-scalar.ts`
|
||||
|
||||
**Step 3: Simplify renderer.html to only use RapiDoc**
|
||||
|
||||
Replace the content of `layouts/partials/api/renderer.html` with:
|
||||
|
||||
```html
|
||||
{{/*
|
||||
API Renderer
|
||||
|
||||
Renders API documentation using RapiDoc.
|
||||
|
||||
Required page params:
|
||||
- staticFilePath: Path to the OpenAPI specification file
|
||||
*/}}
|
||||
|
||||
{{ partial "api/rapidoc.html" . }}
|
||||
```
|
||||
|
||||
**Step 4: Remove ApiScalar from main.js**
|
||||
|
||||
Remove the import line:
|
||||
|
||||
```javascript
|
||||
import ApiScalar from './components/api-scalar.ts';
|
||||
```
|
||||
|
||||
Remove the registry entry:
|
||||
|
||||
```javascript
|
||||
'api-scalar': ApiScalar,
|
||||
```
|
||||
|
||||
**Step 5: Remove apiRenderer config from hugo.yml**
|
||||
|
||||
Remove these lines from `config/_default/hugo.yml`:
|
||||
|
||||
```yaml
|
||||
# API documentation renderer: "scalar" (default) or "rapidoc"
|
||||
apiRenderer: rapidoc
|
||||
```
|
||||
|
||||
**Step 6: Verify deletions**
|
||||
|
||||
Run: `ls layouts/partials/api/scalar.html assets/js/components/api-scalar.ts 2>&1`
|
||||
Expected: "No such file or directory" for both
|
||||
|
||||
Run: `grep -c "ApiScalar\|api-scalar" assets/js/main.js`
|
||||
Expected: `0`
|
||||
|
||||
**Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "refactor(api): Remove Scalar renderer and related code
|
||||
|
||||
Remove unused Scalar API documentation renderer:
|
||||
- Delete layouts/partials/api/scalar.html
|
||||
- Delete assets/js/components/api-scalar.ts
|
||||
- Simplify renderer.html to only use RapiDoc
|
||||
- Remove ApiScalar from main.js component registry
|
||||
- Remove apiRenderer config option from hugo.yml"
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## Task 5: Remove Deprecated api-tabs Component
|
||||
|
||||
**Files:**
|
||||
|
||||
- Delete: `assets/js/components/api-tabs.ts`
|
||||
- Delete: `layouts/partials/api/tabs.html`
|
||||
- Delete: `layouts/partials/api/tab-panels.html`
|
||||
- Modify: `assets/js/main.js`
|
||||
|
||||
**Step 1: Delete api-tabs.ts**
|
||||
|
||||
Run: `rm assets/js/components/api-tabs.ts`
|
||||
|
||||
**Step 2: Delete tabs.html partial**
|
||||
|
||||
Run: `rm layouts/partials/api/tabs.html`
|
||||
|
||||
**Step 3: Delete tab-panels.html partial**
|
||||
|
||||
Run: `rm layouts/partials/api/tab-panels.html`
|
||||
|
||||
**Step 4: Remove ApiTabs from main.js**
|
||||
|
||||
Remove the import line:
|
||||
|
||||
```javascript
|
||||
import ApiTabs from './components/api-tabs.ts';
|
||||
```
|
||||
|
||||
Remove the registry entry:
|
||||
|
||||
```javascript
|
||||
'api-tabs': ApiTabs,
|
||||
```
|
||||
|
||||
**Step 5: Verify deletions**
|
||||
|
||||
Run: `ls assets/js/components/api-tabs.ts layouts/partials/api/tabs.html layouts/partials/api/tab-panels.html 2>&1`
|
||||
Expected: "No such file or directory" for all
|
||||
|
||||
Run: `grep -c "ApiTabs\|api-tabs" assets/js/main.js`
|
||||
Expected: `0`
|
||||
|
||||
**Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "refactor(api): Remove deprecated api-tabs component
|
||||
|
||||
Remove unused tabs component and partials:
|
||||
- Delete assets/js/components/api-tabs.ts
|
||||
- Delete layouts/partials/api/tabs.html
|
||||
- Delete layouts/partials/api/tab-panels.html
|
||||
- Remove ApiTabs from main.js component registry
|
||||
|
||||
The new architecture renders content directly without tabs."
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## Task 6: Update Cypress Tests
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `cypress/e2e/content/api-reference.cy.js`
|
||||
|
||||
**Step 1: Remove tab-related tests**
|
||||
|
||||
Remove the tests that reference tabs, tab-panels, and the 3-column layout tests that expect tabs. The tests for the basic API reference pages and RapiDoc Mini should remain.
|
||||
|
||||
Specifically, remove:
|
||||
|
||||
1. The `describe('API reference 3-column layout', ...)` block that tests tabs (lines 134-275)
|
||||
2. Keep the basic API reference tests and RapiDoc Mini tests
|
||||
|
||||
**Step 2: Verify tests still work**
|
||||
|
||||
Run: `node cypress/support/run-e2e-specs.js --spec "cypress/e2e/content/api-reference.cy.js" content/influxdb3/core/api/_index.md`
|
||||
Expected: Tests pass
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add cypress/e2e/content/api-reference.cy.js
|
||||
git commit -m "test(api): Remove deprecated tab-related tests
|
||||
|
||||
Remove tests for tabs and 3-column layout that no longer exist.
|
||||
Keep basic API reference tests and RapiDoc Mini component tests."
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## Task 7: Run Full Test Suite and Verify
|
||||
|
||||
**Files:**
|
||||
|
||||
- None (verification only)
|
||||
|
||||
**Step 1: Build Hugo site**
|
||||
|
||||
Run: `npx hugo --quiet`
|
||||
Expected: Build succeeds without errors
|
||||
|
||||
**Step 2: Start Hugo server**
|
||||
|
||||
Run: `npx hugo server &`
|
||||
Expected: Server starts on port 1313
|
||||
|
||||
**Step 3: Test API pages load correctly**
|
||||
|
||||
Run: `curl -s -o /dev/null -w "%{http_code}" http://localhost:1313/influxdb3/core/api/`
|
||||
Expected: `200`
|
||||
|
||||
Run: `curl -s -o /dev/null -w "%{http_code}" http://localhost:1313/influxdb3/core/api/write/post/`
|
||||
Expected: `200`
|
||||
|
||||
**Step 4: Run Cypress API tests**
|
||||
|
||||
Run: `node cypress/support/run-e2e-specs.js --spec "cypress/e2e/content/api-reference.cy.js" content/influxdb3/core/api/_index.md`
|
||||
Expected: All tests pass
|
||||
|
||||
**Step 5: Stop Hugo server**
|
||||
|
||||
Run: `pkill -f "hugo server"`
|
||||
|
||||
**Step 6: Final commit if any fixes needed**
|
||||
|
||||
```bash
|
||||
git status
|
||||
# If any files need fixing, make changes and commit
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## Summary
|
||||
|
||||
This plan:
|
||||
|
||||
1. Creates `api-rapidoc.ts` - a TypeScript component extracting \~230 lines of inline JS from rapidoc.html
|
||||
2. Registers the new component in main.js
|
||||
3. Updates rapidoc.html to use `data-component` pattern instead of inline scripts
|
||||
4. Removes unused Scalar renderer code (scalar.html, api-scalar.ts, config option)
|
||||
5. Removes deprecated tabs components (api-tabs.ts, tabs.html, tab-panels.html)
|
||||
6. Updates Cypress tests to remove tab-related tests
|
||||
7. Verifies everything works with Hugo build and test suite
|
||||
|
||||
Total files changed:
|
||||
|
||||
- 1 file created: `assets/js/components/api-rapidoc.ts`
|
||||
- 5 files deleted: scalar.html, api-scalar.ts, api-tabs.ts, tabs.html, tab-panels.html
|
||||
- 4 files modified: rapidoc.html, renderer.html, main.js, hugo.yml, api-reference.cy.js
|
||||
|
|
@ -1,23 +1,10 @@
|
|||
{{/*
|
||||
API Renderer Abstraction
|
||||
API Renderer
|
||||
|
||||
Selects and loads the appropriate API documentation renderer.
|
||||
RapiDoc is the primary renderer.
|
||||
Renders API documentation using RapiDoc.
|
||||
|
||||
Required page params:
|
||||
- staticFilePath: Path to the OpenAPI specification file
|
||||
|
||||
Site params:
|
||||
- apiRenderer: "rapidoc" (default) or "scalar"
|
||||
*/}}
|
||||
|
||||
{{ $renderer := site.Params.apiRenderer | default "rapidoc" }}
|
||||
|
||||
{{ if eq $renderer "rapidoc" }}
|
||||
{{ partial "api/rapidoc.html" . }}
|
||||
{{ else if eq $renderer "scalar" }}
|
||||
{{ partial "api/scalar.html" . }}
|
||||
{{ else }}
|
||||
{{/* Fallback to rapidoc if unknown renderer specified */}}
|
||||
{{ partial "api/rapidoc.html" . }}
|
||||
{{ end }}
|
||||
{{ partial "api/rapidoc.html" . }}
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
{{/*
|
||||
Scalar API Documentation Renderer
|
||||
|
||||
Modern, accessible API documentation powered by Scalar.
|
||||
Features:
|
||||
- Dark/light theme support synchronized with site theme
|
||||
- InfluxData brand colors
|
||||
- Responsive layout
|
||||
- AI agent spec discovery via link[rel=alternate]
|
||||
- Download link for OpenAPI spec
|
||||
|
||||
Required page params:
|
||||
- staticFilePath: Path to the OpenAPI specification file
|
||||
*/}}
|
||||
|
||||
{{ $specPath := .Params.staticFilePath }}
|
||||
{{ $specPathJSON := replace $specPath ".yaml" ".json" | replace ".yml" ".json" }}
|
||||
|
||||
{{/* 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">
|
||||
{{/* Download button is now in single.html header row above tabs */}}
|
||||
|
||||
{{/* Scalar API Reference container (component-driven) */}}
|
||||
<div
|
||||
class="api-reference-container"
|
||||
data-component="api-scalar"
|
||||
data-spec-path="{{ $specPath }}"
|
||||
data-cdn="https://cdn.jsdelivr.net/npm/@scalar/api-reference@latest"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.api-reference-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.api-reference-container {
|
||||
min-height: 600px;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue