style(api): remove RapiDoc styles and rename hugo-native to operations
Remove all RapiDoc-specific code from styles and JavaScript: - Delete rapi-doc::part() CSS selectors from _api-layout.scss - Delete dead auth modal styles from _api-security-schemes.scss - Delete api-auth-input.ts component (RapiDoc "Try it" integration) - Delete static/css/rapidoc-custom.css (unused) - Remove setupRapiDocNavigation() from api-toc.ts - Update comments to remove RapiDoc references Rename _api-hugo-native.scss to _api-operations.scss since Hugo-native is now the standard (not a POC).claude/api-code-samples-plan-MEkQO
parent
242989b7db
commit
3a56561cfa
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
branch: feat-api-uplift
|
||||
repo: docs-v2
|
||||
created: 2025-12-02T15:28:32Z
|
||||
status: in-progress
|
||||
---
|
||||
|
||||
# feat-api-uplift
|
||||
|
||||
## Overview
|
||||
|
||||
Replace the current API reference documentation implementation (RapiDoc web components) with Hugo-native templates.
|
||||
|
||||
## Phase 1: Core Infrastructure (completed)
|
||||
|
||||
### Build process
|
||||
|
||||
- `yarn build:api` parses OpenAPI specs into Hugo data
|
||||
- Generates Hugo pages with frontmatter for Algolia search integration
|
||||
- Static JSON chunks for faster page loads
|
||||
|
||||
### OpenAPI tag cleanup
|
||||
|
||||
- Removed unused tags from OpenAPI specs
|
||||
- Updated tags to be consistent and descriptive
|
||||
|
||||
### Hugo-native POC
|
||||
|
||||
- Implemented Hugo-native templates in `layouts/partials/api/hugo-native/`
|
||||
- Tested with InfluxDB 3 Core product
|
||||
|
||||
## Phase 2: Migration to Hugo-Native (in progress)
|
||||
|
||||
**Plan**: @plans/2026-02-13-hugo-native-api-migration.md
|
||||
|
||||
### Task Order
|
||||
|
||||
1. ✅ **Promote Hugo-native templates** - Move from POC to production
|
||||
2. ✅ **Remove RapiDoc templates** - Delete templates and partials
|
||||
3. ✅ **Remove RapiDoc JavaScript** - Delete components
|
||||
4. ✅ **Remove operation pages** - Delete individual operation page generation
|
||||
5. ✅ **Update Cypress tests** - Simplify tests for static HTML
|
||||
6. ✅ **Clean up styles** - Remove RapiDoc CSS and dead auth modal code
|
||||
7. **Fix generation script cleanup** - Add `--clean` flag (planned)
|
||||
8. **Apply Cache Data tag split** - Enterprise spec update (planned)
|
||||
9. **Migrate remaining products** - Apply to all InfluxDB products (planned)
|
||||
|
||||
## Related Files
|
||||
|
||||
- Branch: `feat-api-uplift`
|
||||
- Plan: `plans/2026-02-13-hugo-native-api-migration.md`
|
||||
|
||||
## Notes
|
||||
|
||||
- Use Chrome devtools and Cypress to debug
|
||||
- No individual operation pages - operations accessed only via tag pages
|
||||
|
|
@ -157,7 +157,7 @@ function generateDataFromOpenAPI(specFile, dataOutPath, articleOutPath) {
|
|||
* Generate Hugo content pages from article data
|
||||
*
|
||||
* Creates markdown files with frontmatter from article metadata.
|
||||
* Each article becomes a page with type: api that renders via RapiDoc.
|
||||
* Each article becomes a page with type: api that renders via Hugo-native templates.
|
||||
*
|
||||
* @param options - Generation options
|
||||
*/
|
||||
|
|
@ -278,7 +278,7 @@ ${yaml.dump(frontmatter)}---
|
|||
* Generate Hugo content pages from tag-based article data
|
||||
*
|
||||
* Creates markdown files with frontmatter from article metadata.
|
||||
* Each article becomes a page with type: api that renders via RapiDoc.
|
||||
* Each article becomes a page with type: api that renders via Hugo-native templates.
|
||||
* Includes operation metadata for TOC generation.
|
||||
*
|
||||
* @param options - Generation options
|
||||
|
|
@ -449,165 +449,9 @@ ${yaml.dump(frontmatter)}---
|
|||
}
|
||||
console.log(`✓ Generated ${data.articles.length} tag-based content pages in ${contentPath}`);
|
||||
// NOTE: Path page generation is disabled - all operations are now displayed
|
||||
// inline on tag pages using RapiDoc with hash-based navigation for deep linking.
|
||||
// The tag pages render all operations in a single scrollable view with a
|
||||
// server-side generated TOC for quick navigation.
|
||||
//
|
||||
// Previously this generated individual pages per API path:
|
||||
// generatePathPages({ articlesPath, contentPath, pathSpecFiles });
|
||||
}
|
||||
/**
|
||||
* Convert API path to URL-safe slug with normalized version prefix
|
||||
*
|
||||
* Transforms an API path to a URL-friendly format:
|
||||
* - Removes leading "/api" prefix (added by parent directory structure)
|
||||
* - Ensures all paths have a version prefix (defaults to v1 if none)
|
||||
* - Removes leading slash
|
||||
* - Removes curly braces from path parameters (e.g., {db} → db)
|
||||
*
|
||||
* Examples:
|
||||
* - "/write" → "v1/write"
|
||||
* - "/api/v3/configure/database" → "v3/configure/database"
|
||||
* - "/api/v3/configure/database/{db}" → "v3/configure/database/db"
|
||||
* - "/api/v2/write" → "v2/write"
|
||||
* - "/health" → "v1/health"
|
||||
*
|
||||
* @param apiPath - The API path (e.g., "/write", "/api/v3/write_lp")
|
||||
* @returns URL-safe path slug with version prefix (e.g., "v1/write", "v3/configure/database")
|
||||
*/
|
||||
function apiPathToSlug(apiPath) {
|
||||
// Remove leading "/api" prefix if present
|
||||
let normalizedPath = apiPath.replace(/^\/api/, '');
|
||||
// Remove leading slash
|
||||
normalizedPath = normalizedPath.replace(/^\//, '');
|
||||
// If path doesn't start with version prefix, add v1/
|
||||
if (!/^v\d+\//.test(normalizedPath)) {
|
||||
normalizedPath = `v1/${normalizedPath}`;
|
||||
}
|
||||
// Remove curly braces from path parameters (e.g., {db} → db)
|
||||
// to avoid URL encoding issues in Hugo
|
||||
normalizedPath = normalizedPath.replace(/[{}]/g, '');
|
||||
return normalizedPath;
|
||||
}
|
||||
/** Method sort order for consistent display */
|
||||
const METHOD_ORDER = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
|
||||
/**
|
||||
* Generate standalone Hugo content pages for each API path
|
||||
*
|
||||
* Creates individual pages at path-based URLs like /api/v3/configure/database/
|
||||
* for each unique API path. Each page includes all HTTP methods (operations)
|
||||
* for that path, rendered using RapiDoc with match-type='includes'.
|
||||
*
|
||||
* When pathSpecFiles is provided, uses path-specific specs for isolated rendering.
|
||||
* Falls back to tag-based specs when pathSpecFiles is not available.
|
||||
*
|
||||
* @param options - Generation options
|
||||
*/
|
||||
function generatePathPages(options) {
|
||||
const { articlesPath, contentPath, pathSpecFiles } = options;
|
||||
const yaml = require('js-yaml');
|
||||
const articlesFile = path.join(articlesPath, 'articles.yml');
|
||||
if (!fs.existsSync(articlesFile)) {
|
||||
console.warn(`⚠️ Articles file not found: ${articlesFile}`);
|
||||
return;
|
||||
}
|
||||
// Read articles data
|
||||
const articlesContent = fs.readFileSync(articlesFile, 'utf8');
|
||||
const data = yaml.load(articlesContent);
|
||||
if (!data.articles || !Array.isArray(data.articles)) {
|
||||
console.warn(`⚠️ No articles found in ${articlesFile}`);
|
||||
return;
|
||||
}
|
||||
// Collect all operations and group by API path
|
||||
const pathOperations = new Map();
|
||||
// Process each article (tag) and collect operations by path
|
||||
for (const article of data.articles) {
|
||||
// Skip conceptual articles (they don't have operations)
|
||||
if (article.fields.isConceptual) {
|
||||
continue;
|
||||
}
|
||||
const operations = article.fields.operations || [];
|
||||
const tagSpecFile = article.fields.staticFilePath;
|
||||
const tagName = article.fields.tag || article.fields.name || '';
|
||||
for (const op of operations) {
|
||||
const existing = pathOperations.get(op.path);
|
||||
if (existing) {
|
||||
// Add operation to existing path group
|
||||
existing.operations.push(op);
|
||||
}
|
||||
else {
|
||||
// Create new path group
|
||||
pathOperations.set(op.path, {
|
||||
operations: [op],
|
||||
tagSpecFile,
|
||||
tagName,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
let pathCount = 0;
|
||||
// Generate a page for each unique API path
|
||||
for (const [apiPath, pathData] of pathOperations) {
|
||||
// Build page path: api/{path}/
|
||||
// e.g., /api/v3/configure/database -> api/v3/configure/database/
|
||||
const pathSlug = apiPathToSlug(apiPath);
|
||||
// Only add 'api/' prefix if the path doesn't already start with 'api/'
|
||||
const basePath = pathSlug.startsWith('api/') ? pathSlug : `api/${pathSlug}`;
|
||||
const pathDir = path.join(contentPath, basePath);
|
||||
const pathFile = path.join(pathDir, '_index.md');
|
||||
// Create directory if needed
|
||||
if (!fs.existsSync(pathDir)) {
|
||||
fs.mkdirSync(pathDir, { recursive: true });
|
||||
}
|
||||
// Sort operations by method order
|
||||
const sortedOperations = [...pathData.operations].sort((a, b) => {
|
||||
const aIndex = METHOD_ORDER.indexOf(a.method.toUpperCase());
|
||||
const bIndex = METHOD_ORDER.indexOf(b.method.toUpperCase());
|
||||
return (aIndex === -1 ? 999 : aIndex) - (bIndex === -1 ? 999 : bIndex);
|
||||
});
|
||||
// Use first operation's summary or construct from methods
|
||||
const methods = sortedOperations.map((op) => op.method.toUpperCase());
|
||||
const title = sortedOperations.length === 1 && sortedOperations[0].summary
|
||||
? sortedOperations[0].summary
|
||||
: `${apiPath}`;
|
||||
// Determine spec file - use path-specific spec if available
|
||||
const pathSpecFile = pathSpecFiles?.get(apiPath);
|
||||
const specFile = pathSpecFile || pathData.tagSpecFile;
|
||||
const frontmatter = {
|
||||
title,
|
||||
description: `API reference for ${apiPath} - ${methods.join(', ')}`,
|
||||
type: 'api-path',
|
||||
layout: 'path',
|
||||
// RapiDoc configuration
|
||||
specFile,
|
||||
apiPath,
|
||||
// Include all operations for TOC generation
|
||||
operations: sortedOperations.map((op) => ({
|
||||
operationId: op.operationId,
|
||||
method: op.method,
|
||||
path: op.path,
|
||||
summary: op.summary,
|
||||
...(op.compatVersion && { compatVersion: op.compatVersion }),
|
||||
})),
|
||||
tag: pathData.tagName,
|
||||
};
|
||||
// Collect related links from all operations
|
||||
const relatedLinks = [];
|
||||
for (const op of sortedOperations) {
|
||||
if (op.externalDocs?.url && !relatedLinks.includes(op.externalDocs.url)) {
|
||||
relatedLinks.push(op.externalDocs.url);
|
||||
}
|
||||
}
|
||||
if (relatedLinks.length > 0) {
|
||||
frontmatter.related = relatedLinks;
|
||||
}
|
||||
const pageContent = `---
|
||||
${yaml.dump(frontmatter)}---
|
||||
`;
|
||||
fs.writeFileSync(pathFile, pageContent);
|
||||
pathCount++;
|
||||
}
|
||||
console.log(`✓ Generated ${pathCount} path pages in ${contentPath}/api/`);
|
||||
// inline on tag pages using Hugo-native templates with hash-based navigation
|
||||
// for deep linking. The tag pages render all operations in a single scrollable
|
||||
// view with a server-side generated TOC for quick navigation.
|
||||
}
|
||||
/**
|
||||
* Merge article data from multiple specs into a single articles.yml
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,604 +0,0 @@
|
|||
/**
|
||||
* API Auth Input Component (Popover)
|
||||
*
|
||||
* Provides a popover-based credential input for API operations.
|
||||
* Integrates with RapiDoc's auth system via JavaScript API.
|
||||
*
|
||||
* Features:
|
||||
* - Popover UI triggered by button click
|
||||
* - Filters auth schemes based on operation requirements
|
||||
* - Session-only credentials (not persisted to storage)
|
||||
* - Syncs with RapiDoc's "Try it" feature
|
||||
*
|
||||
* Usage:
|
||||
* <button data-component="api-auth-input"
|
||||
* data-schemes="bearer,token"
|
||||
* data-popover="true">
|
||||
* Set credentials
|
||||
* </button>
|
||||
* <div class="api-auth-popover" hidden></div>
|
||||
*/
|
||||
|
||||
interface ComponentOptions {
|
||||
component: HTMLElement;
|
||||
}
|
||||
|
||||
interface AuthCredentials {
|
||||
bearer?: string;
|
||||
basic?: { username: string; password: string };
|
||||
querystring?: string;
|
||||
}
|
||||
|
||||
type CleanupFn = () => void;
|
||||
|
||||
// sessionStorage key for credentials
|
||||
// Persists across page navigations, cleared when tab closes
|
||||
const CREDENTIALS_KEY = 'influxdata-api-credentials';
|
||||
|
||||
/**
|
||||
* Get credentials from sessionStorage
|
||||
*/
|
||||
function getCredentials(): AuthCredentials {
|
||||
try {
|
||||
const stored = sessionStorage.getItem(CREDENTIALS_KEY);
|
||||
return stored ? JSON.parse(stored) : {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set credentials in sessionStorage
|
||||
*/
|
||||
function setCredentials(credentials: AuthCredentials): void {
|
||||
try {
|
||||
if (Object.keys(credentials).length === 0) {
|
||||
sessionStorage.removeItem(CREDENTIALS_KEY);
|
||||
} else {
|
||||
sessionStorage.setItem(CREDENTIALS_KEY, JSON.stringify(credentials));
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[API Auth] Failed to store credentials:', e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any credentials are set
|
||||
*/
|
||||
function hasCredentials(): boolean {
|
||||
const creds = getCredentials();
|
||||
return !!(creds.bearer || creds.basic?.password || creds.querystring);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to update the visible auth input in RapiDoc's shadow DOM.
|
||||
* This provides visual feedback but is not essential for authentication.
|
||||
*/
|
||||
function updateRapiDocAuthInput(
|
||||
rapiDoc: HTMLElement,
|
||||
token: string,
|
||||
scheme: 'bearer' | 'token'
|
||||
): void {
|
||||
try {
|
||||
const shadowRoot = rapiDoc.shadowRoot;
|
||||
if (!shadowRoot) return;
|
||||
|
||||
const headerValue =
|
||||
scheme === 'bearer' ? `Bearer ${token}` : `Token ${token}`;
|
||||
|
||||
const authInputSelectors = [
|
||||
'input[data-pname="Authorization"]',
|
||||
'input[placeholder*="authorization" i]',
|
||||
'input[placeholder*="token" i]',
|
||||
'.request-headers input[type="text"]',
|
||||
];
|
||||
|
||||
for (const selector of authInputSelectors) {
|
||||
const input = shadowRoot.querySelector<HTMLInputElement>(selector);
|
||||
if (input && !input.value) {
|
||||
input.value = headerValue;
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
console.log('[API Auth] Updated visible auth input in RapiDoc');
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.debug('[API Auth] Could not update visible input:', e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply credentials to a RapiDoc element
|
||||
* Returns true if credentials were successfully applied
|
||||
*/
|
||||
function applyCredentialsToRapiDoc(
|
||||
rapiDoc: HTMLElement,
|
||||
credentials: AuthCredentials
|
||||
): boolean {
|
||||
let applied = false;
|
||||
|
||||
// Clear existing credentials first
|
||||
if ('removeAllSecurityKeys' in rapiDoc) {
|
||||
try {
|
||||
(rapiDoc as any).removeAllSecurityKeys();
|
||||
} catch (e) {
|
||||
console.warn('[API Auth] Failed to clear existing credentials:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply bearer/token credentials using setApiKey() only
|
||||
// Using both HTML attributes AND setApiKey() causes "2 API keys applied"
|
||||
if (credentials.bearer) {
|
||||
try {
|
||||
if ('setApiKey' in rapiDoc) {
|
||||
(rapiDoc as any).setApiKey('BearerAuthentication', credentials.bearer);
|
||||
console.log('[API Auth] Applied bearer via setApiKey()');
|
||||
applied = true;
|
||||
}
|
||||
updateRapiDocAuthInput(rapiDoc, credentials.bearer, 'bearer');
|
||||
} catch (e) {
|
||||
console.error('[API Auth] Failed to set API key:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply basic auth credentials
|
||||
if ('setHttpUserNameAndPassword' in rapiDoc && credentials.basic?.password) {
|
||||
try {
|
||||
(rapiDoc as any).setHttpUserNameAndPassword(
|
||||
'BasicAuthentication',
|
||||
credentials.basic.username || '',
|
||||
credentials.basic.password
|
||||
);
|
||||
applied = true;
|
||||
console.log('[API Auth] Applied basic auth credentials to RapiDoc');
|
||||
} catch (e) {
|
||||
console.error('[API Auth] Failed to set basic auth:', e);
|
||||
}
|
||||
}
|
||||
|
||||
return applied;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create auth field HTML for a specific scheme
|
||||
*/
|
||||
function createAuthField(scheme: string): string {
|
||||
switch (scheme) {
|
||||
case 'bearer':
|
||||
return `
|
||||
<div class="auth-field" data-scheme="bearer">
|
||||
<label for="auth-bearer">
|
||||
<span class="auth-label-text">API Token</span>
|
||||
<span class="auth-label-hint">(Bearer auth)</span>
|
||||
</label>
|
||||
<div class="auth-input-group">
|
||||
<input type="password"
|
||||
id="auth-bearer"
|
||||
placeholder="Enter your API token"
|
||||
autocomplete="off" />
|
||||
<button type="button" class="auth-show-toggle" data-target="auth-bearer" aria-label="Show token">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 3C4.5 3 1.5 5.5 0 8c1.5 2.5 4.5 5 8 5s6.5-2.5 8-5c-1.5-2.5-4.5-5-8-5zm0 8c-1.7 0-3-1.3-3-3s1.3-3 3-3 3 1.3 3 3-1.3 3-3 3z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
case 'token':
|
||||
return `
|
||||
<div class="auth-field" data-scheme="token">
|
||||
<label for="auth-token">
|
||||
<span class="auth-label-text">API Token</span>
|
||||
<span class="auth-label-hint">(v2 Token scheme)</span>
|
||||
</label>
|
||||
<div class="auth-input-group">
|
||||
<input type="password"
|
||||
id="auth-token"
|
||||
placeholder="Enter your API token"
|
||||
autocomplete="off" />
|
||||
<button type="button" class="auth-show-toggle" data-target="auth-token" aria-label="Show token">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 3C4.5 3 1.5 5.5 0 8c1.5 2.5 4.5 5 8 5s6.5-2.5 8-5c-1.5-2.5-4.5-5-8-5zm0 8c-1.7 0-3-1.3-3-3s1.3-3 3-3 3 1.3 3 3-1.3 3-3 3z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
case 'basic':
|
||||
return `
|
||||
<div class="auth-field-group" data-scheme="basic">
|
||||
<p class="auth-group-label">Basic Authentication <span class="auth-label-hint">(v1 compatibility)</span></p>
|
||||
<div class="auth-field">
|
||||
<label for="auth-username">Username</label>
|
||||
<input type="text"
|
||||
id="auth-username"
|
||||
placeholder="Optional"
|
||||
autocomplete="username" />
|
||||
</div>
|
||||
<div class="auth-field">
|
||||
<label for="auth-password">Password / Token</label>
|
||||
<div class="auth-input-group">
|
||||
<input type="password"
|
||||
id="auth-password"
|
||||
placeholder="Enter token"
|
||||
autocomplete="current-password" />
|
||||
<button type="button" class="auth-show-toggle" data-target="auth-password" aria-label="Show password">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 3C4.5 3 1.5 5.5 0 8c1.5 2.5 4.5 5 8 5s6.5-2.5 8-5c-1.5-2.5-4.5-5-8-5zm0 8c-1.7 0-3-1.3-3-3s1.3-3 3-3 3 1.3 3 3-1.3 3-3 3z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
case 'querystring':
|
||||
return `
|
||||
<div class="auth-field" data-scheme="querystring">
|
||||
<label for="auth-querystring">
|
||||
<span class="auth-label-text">Query Parameter Token</span>
|
||||
<span class="auth-label-hint">(v1 ?p=TOKEN)</span>
|
||||
</label>
|
||||
<div class="auth-input-group">
|
||||
<input type="password"
|
||||
id="auth-querystring"
|
||||
placeholder="Enter token for ?p= parameter"
|
||||
autocomplete="off" />
|
||||
<button type="button" class="auth-show-toggle" data-target="auth-querystring" aria-label="Show token">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 3C4.5 3 1.5 5.5 0 8c1.5 2.5 4.5 5 8 5s6.5-2.5 8-5c-1.5-2.5-4.5-5-8-5zm0 8c-1.7 0-3-1.3-3-3s1.3-3 3-3 3 1.3 3 3-1.3 3-3 3z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the popover content HTML
|
||||
*/
|
||||
function createPopoverContent(schemes: string[]): string {
|
||||
// If both bearer and token are supported, show combined field
|
||||
const hasBearerAndToken =
|
||||
schemes.includes('bearer') && schemes.includes('token');
|
||||
const displaySchemes = hasBearerAndToken
|
||||
? schemes.filter((s) => s !== 'token')
|
||||
: schemes;
|
||||
|
||||
const fields = displaySchemes.map((s) => createAuthField(s)).join('');
|
||||
|
||||
// Adjust label if both bearer and token are supported
|
||||
const bearerLabel = hasBearerAndToken
|
||||
? '(Bearer / Token auth)'
|
||||
: '(Bearer auth)';
|
||||
|
||||
return `
|
||||
<div class="api-auth-popover-content">
|
||||
<div class="popover-header">
|
||||
<h4>API Credentials</h4>
|
||||
<button type="button" class="popover-close" aria-label="Close">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<p class="auth-description">
|
||||
Enter credentials for "Try it" requests.
|
||||
</p>
|
||||
${fields.replace('(Bearer auth)', bearerLabel)}
|
||||
<div class="auth-actions">
|
||||
<button type="button" class="btn btn-sm auth-apply">Apply</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary auth-clear">Clear</button>
|
||||
</div>
|
||||
<p class="auth-feedback" hidden></p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show feedback message
|
||||
*/
|
||||
function showFeedback(
|
||||
container: HTMLElement,
|
||||
message: string,
|
||||
type: 'success' | 'error'
|
||||
): void {
|
||||
const feedback = container.querySelector<HTMLElement>('.auth-feedback');
|
||||
if (feedback) {
|
||||
feedback.textContent = message;
|
||||
feedback.className = `auth-feedback auth-feedback--${type}`;
|
||||
feedback.hidden = false;
|
||||
|
||||
setTimeout(() => {
|
||||
feedback.hidden = true;
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the status indicator on the trigger button
|
||||
*/
|
||||
function updateStatusIndicator(trigger: HTMLElement): void {
|
||||
const indicator = trigger.querySelector<HTMLElement>(
|
||||
'.auth-status-indicator'
|
||||
);
|
||||
const hasCreds = hasCredentials();
|
||||
|
||||
if (indicator) {
|
||||
indicator.hidden = !hasCreds;
|
||||
}
|
||||
|
||||
trigger.classList.toggle('has-credentials', hasCreds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the auth input popover component
|
||||
*/
|
||||
export default function ApiAuthInput({
|
||||
component,
|
||||
}: ComponentOptions): CleanupFn | void {
|
||||
// Component is the banner container, find the trigger button inside it
|
||||
const triggerEl =
|
||||
component.querySelector<HTMLButtonElement>('.api-auth-trigger');
|
||||
const statusEl = component.querySelector<HTMLElement>('.api-auth-status');
|
||||
|
||||
if (!triggerEl) {
|
||||
console.error('[API Auth] Trigger button not found in banner');
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the popover element (it's a sibling before the banner)
|
||||
const popoverEl = document.querySelector<HTMLElement>('.api-auth-popover');
|
||||
|
||||
if (!popoverEl) {
|
||||
console.error('[API Auth] Popover container not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Reassign to new consts so TypeScript knows they're not null in closures
|
||||
const trigger = triggerEl;
|
||||
const popover = popoverEl;
|
||||
|
||||
// Get schemes from the component (banner) dataset
|
||||
const schemesAttr = component.dataset.schemes || 'bearer';
|
||||
const schemes = schemesAttr.split(',').map((s) => s.trim().toLowerCase());
|
||||
|
||||
// Render popover content
|
||||
popover.innerHTML = createPopoverContent(schemes);
|
||||
|
||||
// Element references
|
||||
const bearerInput = popover.querySelector<HTMLInputElement>('#auth-bearer');
|
||||
const tokenInput = popover.querySelector<HTMLInputElement>('#auth-token');
|
||||
const usernameInput =
|
||||
popover.querySelector<HTMLInputElement>('#auth-username');
|
||||
const passwordInput =
|
||||
popover.querySelector<HTMLInputElement>('#auth-password');
|
||||
const querystringInput =
|
||||
popover.querySelector<HTMLInputElement>('#auth-querystring');
|
||||
const applyBtn = popover.querySelector<HTMLButtonElement>('.auth-apply');
|
||||
const clearBtn = popover.querySelector<HTMLButtonElement>('.auth-clear');
|
||||
const closeBtn = popover.querySelector<HTMLButtonElement>('.popover-close');
|
||||
|
||||
// Backdrop element for modal overlay
|
||||
const backdrop = document.querySelector<HTMLElement>('.api-auth-backdrop');
|
||||
|
||||
// Restore saved credentials from sessionStorage
|
||||
const savedCredentials = getCredentials();
|
||||
if (savedCredentials.bearer && bearerInput) {
|
||||
bearerInput.value = savedCredentials.bearer;
|
||||
}
|
||||
if (savedCredentials.basic) {
|
||||
if (usernameInput) usernameInput.value = savedCredentials.basic.username;
|
||||
if (passwordInput) passwordInput.value = savedCredentials.basic.password;
|
||||
}
|
||||
if (savedCredentials.querystring && querystringInput) {
|
||||
querystringInput.value = savedCredentials.querystring;
|
||||
}
|
||||
|
||||
// Update status indicator based on saved credentials
|
||||
if (hasCredentials() && statusEl) {
|
||||
statusEl.textContent = 'Credentials set for this session.';
|
||||
trigger.textContent = 'Update credentials';
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle popover visibility
|
||||
*/
|
||||
function togglePopover(show?: boolean): void {
|
||||
const shouldShow = show ?? popover.hidden;
|
||||
popover.hidden = !shouldShow;
|
||||
if (backdrop) backdrop.hidden = !shouldShow;
|
||||
trigger.setAttribute('aria-expanded', String(shouldShow));
|
||||
|
||||
if (shouldShow) {
|
||||
// Focus first input when opening
|
||||
const firstInput = popover.querySelector<HTMLInputElement>(
|
||||
'input:not([type="hidden"])'
|
||||
);
|
||||
firstInput?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close popover
|
||||
*/
|
||||
function closePopover(): void {
|
||||
togglePopover(false);
|
||||
trigger.focus();
|
||||
}
|
||||
|
||||
// Trigger button click
|
||||
trigger.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
togglePopover();
|
||||
});
|
||||
|
||||
// Close button
|
||||
closeBtn?.addEventListener('click', closePopover);
|
||||
|
||||
// Close on backdrop click
|
||||
backdrop?.addEventListener('click', closePopover);
|
||||
|
||||
// Close on outside click
|
||||
function handleOutsideClick(e: MouseEvent): void {
|
||||
if (
|
||||
!popover.hidden &&
|
||||
!popover.contains(e.target as Node) &&
|
||||
!trigger.contains(e.target as Node)
|
||||
) {
|
||||
closePopover();
|
||||
}
|
||||
}
|
||||
document.addEventListener('click', handleOutsideClick);
|
||||
|
||||
// Close on Escape
|
||||
function handleEscape(e: KeyboardEvent): void {
|
||||
if (e.key === 'Escape' && !popover.hidden) {
|
||||
closePopover();
|
||||
}
|
||||
}
|
||||
document.addEventListener('keydown', handleEscape);
|
||||
|
||||
// Show/hide toggle for password fields
|
||||
const showToggles =
|
||||
popover.querySelectorAll<HTMLButtonElement>('.auth-show-toggle');
|
||||
showToggles.forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
const targetId = btn.dataset.target;
|
||||
const input = popover.querySelector<HTMLInputElement>(`#${targetId}`);
|
||||
if (input) {
|
||||
const isPassword = input.type === 'password';
|
||||
input.type = isPassword ? 'text' : 'password';
|
||||
btn.classList.toggle('showing', !isPassword);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Apply credentials
|
||||
*/
|
||||
function applyCredentials(): void {
|
||||
const newCredentials: AuthCredentials = {};
|
||||
|
||||
// Get token from bearer or token input (they're combined for UX)
|
||||
const tokenValue = bearerInput?.value || tokenInput?.value;
|
||||
if (tokenValue) {
|
||||
newCredentials.bearer = tokenValue;
|
||||
}
|
||||
|
||||
if (usernameInput?.value || passwordInput?.value) {
|
||||
newCredentials.basic = {
|
||||
username: usernameInput?.value || '',
|
||||
password: passwordInput?.value || '',
|
||||
};
|
||||
}
|
||||
|
||||
if (querystringInput?.value) {
|
||||
newCredentials.querystring = querystringInput.value;
|
||||
}
|
||||
|
||||
setCredentials(newCredentials);
|
||||
updateStatusIndicator(trigger);
|
||||
|
||||
// Update status text and button
|
||||
if (hasCredentials()) {
|
||||
if (statusEl) statusEl.textContent = 'Credentials set for this session.';
|
||||
trigger.textContent = 'Update credentials';
|
||||
}
|
||||
|
||||
// Apply to RapiDoc
|
||||
const rapiDoc = document.querySelector('rapi-doc') as HTMLElement | null;
|
||||
if (rapiDoc && 'setApiKey' in rapiDoc) {
|
||||
const applied = applyCredentialsToRapiDoc(rapiDoc, newCredentials);
|
||||
if (applied) {
|
||||
showFeedback(popover, 'Credentials saved for this session', 'success');
|
||||
} else {
|
||||
showFeedback(popover, 'No credentials to apply', 'error');
|
||||
}
|
||||
} else {
|
||||
showFeedback(popover, 'Credentials saved for this session', 'success');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear credentials
|
||||
*/
|
||||
function clearCredentials(): void {
|
||||
if (bearerInput) bearerInput.value = '';
|
||||
if (tokenInput) tokenInput.value = '';
|
||||
if (usernameInput) usernameInput.value = '';
|
||||
if (passwordInput) passwordInput.value = '';
|
||||
if (querystringInput) querystringInput.value = '';
|
||||
|
||||
setCredentials({});
|
||||
updateStatusIndicator(trigger);
|
||||
|
||||
// Reset status text and button
|
||||
if (statusEl)
|
||||
statusEl.textContent = 'This endpoint requires authentication.';
|
||||
trigger.textContent = 'Set credentials';
|
||||
|
||||
// Clear from RapiDoc using removeAllSecurityKeys()
|
||||
const rapiDoc = document.querySelector('rapi-doc') as HTMLElement | null;
|
||||
if (rapiDoc && 'removeAllSecurityKeys' in rapiDoc) {
|
||||
try {
|
||||
(rapiDoc as any).removeAllSecurityKeys();
|
||||
} catch (e) {
|
||||
console.debug('[API Auth] Failed to clear RapiDoc credentials:', e);
|
||||
}
|
||||
}
|
||||
|
||||
showFeedback(popover, 'Credentials cleared', 'success');
|
||||
}
|
||||
|
||||
// Button handlers
|
||||
applyBtn?.addEventListener('click', applyCredentials);
|
||||
clearBtn?.addEventListener('click', clearCredentials);
|
||||
|
||||
// Listen for RapiDoc spec-loaded event to apply stored credentials
|
||||
function handleSpecLoaded(event: Event): void {
|
||||
const rapiDoc = event.target as HTMLElement;
|
||||
const storedCredentials = getCredentials();
|
||||
if (
|
||||
storedCredentials.bearer ||
|
||||
storedCredentials.basic?.password ||
|
||||
storedCredentials.querystring
|
||||
) {
|
||||
setTimeout(() => {
|
||||
applyCredentialsToRapiDoc(rapiDoc, storedCredentials);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for RapiDoc elements
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
for (const mutation of mutations) {
|
||||
for (const node of mutation.addedNodes) {
|
||||
if (node instanceof HTMLElement && node.tagName === 'RAPI-DOC') {
|
||||
node.addEventListener('spec-loaded', handleSpecLoaded);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
|
||||
// Check if RapiDoc already exists
|
||||
const existingRapiDoc = document.querySelector('rapi-doc');
|
||||
if (existingRapiDoc) {
|
||||
existingRapiDoc.addEventListener('spec-loaded', handleSpecLoaded);
|
||||
}
|
||||
|
||||
// Initialize status indicator
|
||||
updateStatusIndicator(trigger);
|
||||
|
||||
// Cleanup function
|
||||
return (): void => {
|
||||
observer.disconnect();
|
||||
document.removeEventListener('click', handleOutsideClick);
|
||||
document.removeEventListener('keydown', handleEscape);
|
||||
existingRapiDoc?.removeEventListener('spec-loaded', handleSpecLoaded);
|
||||
};
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* Generates "ON THIS PAGE" navigation from content headings or operations data.
|
||||
* Features:
|
||||
* - Builds TOC from h2 headings by default (avoids RapiDoc h3 fragment collisions)
|
||||
* - Builds TOC from h2 headings by default
|
||||
* - Builds TOC from operations data passed via data-operations attribute (tag-based)
|
||||
* - Highlights current section on scroll (intersection observer)
|
||||
* - Smooth scroll to anchors
|
||||
|
|
@ -44,11 +44,7 @@ interface OperationMeta {
|
|||
/**
|
||||
* Get headings from the currently visible content
|
||||
*
|
||||
* For API pages, only h2 headings are collected to avoid fragment collisions
|
||||
* from repetitive h3 headings like "Syntax", "Parameters", "Responses" that
|
||||
* RapiDoc generates for each operation.
|
||||
*
|
||||
* @param maxLevel - Maximum heading level to include (default: 2 for API pages)
|
||||
* @param maxLevel - Maximum heading level to include (default: 2)
|
||||
*/
|
||||
function getVisibleHeadings(maxLevel: number = 2): TocEntry[] {
|
||||
// Find the active tab panel or main content area
|
||||
|
|
@ -147,7 +143,7 @@ function buildOperationsTocHtml(operations: OperationMeta[]): string {
|
|||
let html = '<ul class="api-toc-list api-toc-list--operations">';
|
||||
|
||||
operations.forEach((op) => {
|
||||
// Generate anchor ID from operationId (RapiDoc uses operationId for anchors)
|
||||
// Generate anchor ID from operationId
|
||||
const anchorId = op.operationId;
|
||||
const methodClass = getMethodClass(op.method);
|
||||
|
||||
|
|
@ -269,44 +265,6 @@ function setupScrollHighlighting(
|
|||
return observer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up RapiDoc navigation for TOC links (for tag pages)
|
||||
* Uses RapiDoc's scrollToPath method instead of native scroll
|
||||
*/
|
||||
function setupRapiDocNavigation(container: HTMLElement): void {
|
||||
container.addEventListener('click', (event) => {
|
||||
const target = event.target as HTMLElement;
|
||||
const link = target.closest<HTMLAnchorElement>('.api-toc-link');
|
||||
|
||||
if (!link) {
|
||||
return;
|
||||
}
|
||||
|
||||
const href = link.getAttribute('href');
|
||||
if (!href?.startsWith('#')) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
// Get the path from the hash (e.g., "post-/api/v3/configure/distinct_cache")
|
||||
const path = href.slice(1);
|
||||
|
||||
// Find RapiDoc element and call scrollToPath
|
||||
const rapiDoc = document.querySelector('rapi-doc') as HTMLElement & {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
scrollToPath?: (path: string) => void;
|
||||
};
|
||||
|
||||
if (rapiDoc && typeof rapiDoc.scrollToPath === 'function') {
|
||||
rapiDoc.scrollToPath(path);
|
||||
}
|
||||
|
||||
// Update URL hash
|
||||
history.pushState(null, '', href);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up smooth scroll for TOC links
|
||||
*/
|
||||
|
|
@ -348,7 +306,6 @@ function setupSmoothScroll(container: HTMLElement): void {
|
|||
|
||||
/**
|
||||
* Update TOC visibility based on active tab
|
||||
* Hide TOC for Operations tab (RapiDoc has built-in navigation)
|
||||
*/
|
||||
function updateTocVisibility(container: HTMLElement): void {
|
||||
const operationsPanel = document.querySelector(
|
||||
|
|
@ -417,21 +374,12 @@ export default function ApiToc({ component }: ComponentOptions): void {
|
|||
}
|
||||
|
||||
// Check if TOC was pre-rendered server-side (has existing links)
|
||||
// For tag pages with RapiDoc, the TOC is rendered by Hugo from operations frontmatter
|
||||
const hasServerRenderedToc = nav.querySelectorAll('.api-toc-link').length > 0;
|
||||
|
||||
if (hasServerRenderedToc) {
|
||||
// Server-side TOC exists - just show it and set up navigation
|
||||
component.classList.remove('is-hidden');
|
||||
|
||||
// For tag pages with RapiDoc, use RapiDoc's scrollToPath for navigation
|
||||
// instead of smooth scrolling (which can't access shadow DOM elements)
|
||||
const rapiDocWrapper = document.querySelector('[data-tag-page="true"]');
|
||||
if (rapiDocWrapper) {
|
||||
setupRapiDocNavigation(component);
|
||||
} else {
|
||||
setupSmoothScroll(component);
|
||||
}
|
||||
setupSmoothScroll(component);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -439,7 +387,7 @@ export default function ApiToc({ component }: ComponentOptions): void {
|
|||
const operations = parseOperationsData(component);
|
||||
let observer: IntersectionObserver | null = null;
|
||||
|
||||
// Get max heading level from data attribute (default: 2 to avoid RapiDoc h3 collisions)
|
||||
// Get max heading level from data attribute (default: 2)
|
||||
// Use data-toc-depth="3" to include h3 headings if needed
|
||||
const maxHeadingLevel = parseInt(
|
||||
component.getAttribute('data-toc-depth') || '2',
|
||||
|
|
@ -467,7 +415,6 @@ export default function ApiToc({ component }: ComponentOptions): void {
|
|||
}
|
||||
|
||||
// Otherwise, fall back to heading-based TOC
|
||||
// Use configured max heading level to avoid fragment collisions from RapiDoc h3s
|
||||
const entries = getVisibleHeadings(maxHeadingLevel);
|
||||
if (nav) {
|
||||
nav.innerHTML = buildTocHtml(entries);
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@ import SidebarSearch from './components/sidebar-search.js';
|
|||
import { SidebarToggle } from './sidebar-toggle.js';
|
||||
import Theme from './theme.js';
|
||||
import ThemeSwitch from './theme-switch.js';
|
||||
import ApiAuthInput from './components/api-auth-input.ts';
|
||||
import ApiToc from './components/api-toc.ts';
|
||||
|
||||
/**
|
||||
|
|
@ -79,7 +78,6 @@ const componentRegistry = {
|
|||
'sidebar-toggle': SidebarToggle,
|
||||
theme: Theme,
|
||||
'theme-switch': ThemeSwitch,
|
||||
'api-auth-input': ApiAuthInput,
|
||||
'api-toc': ApiToc,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
padding: 1rem;
|
||||
border-left: 1px solid $nav-border;
|
||||
|
||||
// Hidden state (used when Operations/RapiDoc tab is active)
|
||||
// Hidden state (used when a tab panel hides the TOC)
|
||||
&.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -589,43 +589,6 @@
|
|||
.tab-content:not(:first-of-type) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// RapiDoc container styling
|
||||
rapi-doc {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////// RapiDoc Overrides ///////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Hide RapiDoc's internal navigation (we provide our own)
|
||||
rapi-doc::part(section-navbar) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
// Hide RapiDoc's internal tag headers/titles (we use custom tabs for navigation)
|
||||
// label-tag-title is the "PROCESSING ENGINE" header with auth badges shown in tag groups
|
||||
rapi-doc::part(label-tag-title) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
// Hide RapiDoc's authentication section (we have separate Auth tab)
|
||||
rapi-doc::part(section-auth) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
// Ensure RapiDoc content fills available space
|
||||
rapi-doc::part(section-main-content) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
// Match RapiDoc's operation section styling to our theme
|
||||
rapi-doc::part(section-operations) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Hugo-Native API Documentation Styles
|
||||
// Styled after docusaurus-openapi aesthetic with clean, readable layouts
|
||||
// API Operations Styles
|
||||
// Renders OpenAPI operations, parameters, schemas, and responses
|
||||
|
||||
// Variables
|
||||
$api-border-radius: 6px;
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
// API Documentation Style Overrides
|
||||
//
|
||||
// Provides loading spinner and reusable API-related styles.
|
||||
// Note: Legacy Redoc-specific overrides have been removed in favor of
|
||||
// Scalar/RapiDoc renderers which use CSS custom properties for theming.
|
||||
// Provides loading spinner and reusable HTTP method badge colors.
|
||||
// Used by Hugo-native API templates for consistent styling.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@import "tools/color-palette";
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
//
|
||||
// Styles for security schemes sections displayed on conceptual API pages
|
||||
// (like Authentication). These sections are rendered from OpenAPI spec
|
||||
// securitySchemes using Hugo templates, not RapiDoc.
|
||||
// securitySchemes using Hugo templates.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
.api-security-schemes {
|
||||
|
|
@ -90,296 +90,3 @@ html:has(link[title="dark-theme"]:not([disabled])) {
|
|||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// API Auth Modal - Credential input for operation pages
|
||||
//
|
||||
// Modal UI triggered by "Set credentials" button in auth info banner.
|
||||
// Integrates with RapiDoc "Try it" via JavaScript API.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Backdrop overlay
|
||||
.api-auth-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
z-index: 1000;
|
||||
|
||||
&[hidden] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Credentials modal
|
||||
.api-auth-popover {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 1001;
|
||||
min-width: 320px;
|
||||
max-width: 400px;
|
||||
background: $g20-white;
|
||||
border: 1px solid $g5-pepper;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
|
||||
|
||||
&[hidden] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.api-auth-popover-content {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.popover-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.75rem;
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
color: $article-heading;
|
||||
}
|
||||
}
|
||||
|
||||
.popover-close {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
color: $g9-mountain;
|
||||
transition: all 0.15s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
color: $article-text;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-description {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 0.85rem;
|
||||
color: $g9-mountain;
|
||||
}
|
||||
|
||||
.auth-field {
|
||||
margin-bottom: 0.75rem;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
color: $article-text;
|
||||
}
|
||||
|
||||
.auth-label-hint {
|
||||
font-weight: 400;
|
||||
color: $g9-mountain;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 1px solid $g5-pepper;
|
||||
border-radius: 3px;
|
||||
font-family: inherit;
|
||||
font-size: 0.9rem;
|
||||
background: $g20-white;
|
||||
color: $article-text;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: $b-pool;
|
||||
box-shadow: 0 0 0 2px rgba($b-pool, 0.2);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: $g9-mountain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.auth-input-group {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
input {
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-show-toggle {
|
||||
position: absolute;
|
||||
right: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.25rem;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: $g9-mountain;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.15s;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.showing {
|
||||
color: $b-pool;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
|
||||
.auth-apply,
|
||||
.auth-clear {
|
||||
padding: 0.4rem 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.auth-apply {
|
||||
background: $b-pool;
|
||||
color: $g20-white;
|
||||
border: 1px solid $b-pool;
|
||||
|
||||
&:hover {
|
||||
background: darken($b-pool, 8%);
|
||||
border-color: darken($b-pool, 8%);
|
||||
}
|
||||
}
|
||||
|
||||
.auth-clear {
|
||||
background: transparent;
|
||||
color: $article-text;
|
||||
border: 1px solid $g5-pepper;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-color: $g9-mountain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.auth-feedback {
|
||||
margin: 0.75rem 0 0 0;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
border-radius: 3px;
|
||||
|
||||
&.auth-feedback--success {
|
||||
background: rgba($gr-viridian, 0.1);
|
||||
color: $gr-viridian;
|
||||
}
|
||||
|
||||
&.auth-feedback--error {
|
||||
background: rgba($r-fire, 0.1);
|
||||
color: $r-fire;
|
||||
}
|
||||
}
|
||||
|
||||
// Dark theme for modal
|
||||
[data-theme="dark"],
|
||||
html:has(link[title="dark-theme"]:not([disabled])) {
|
||||
.api-auth-popover {
|
||||
background: $grey15;
|
||||
border-color: $grey25;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.popover-header h4 {
|
||||
color: $g20-white;
|
||||
}
|
||||
|
||||
.popover-close {
|
||||
color: $g15-platinum;
|
||||
|
||||
&:hover {
|
||||
background: $grey20;
|
||||
color: $g20-white;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-description {
|
||||
color: $g15-platinum;
|
||||
}
|
||||
|
||||
.auth-field {
|
||||
label {
|
||||
color: $g20-white;
|
||||
}
|
||||
|
||||
.auth-label-hint {
|
||||
color: $g15-platinum;
|
||||
}
|
||||
|
||||
input {
|
||||
background: $grey20;
|
||||
border-color: $grey25;
|
||||
color: $g20-white;
|
||||
|
||||
&:focus {
|
||||
border-color: $b-pool;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: $g9-mountain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.auth-show-toggle {
|
||||
color: $g15-platinum;
|
||||
|
||||
&.showing {
|
||||
color: $b-pool;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-feedback {
|
||||
&.auth-feedback--success {
|
||||
background: rgba($gr-viridian, 0.15);
|
||||
color: $gr-emerald;
|
||||
}
|
||||
|
||||
&.auth-feedback--error {
|
||||
background: rgba($r-fire, 0.15);
|
||||
color: $r-tungsten;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-actions {
|
||||
.auth-clear {
|
||||
color: $g15-platinum;
|
||||
border-color: $grey25;
|
||||
|
||||
&:hover {
|
||||
background: $grey20;
|
||||
border-color: $g15-platinum;
|
||||
color: $g20-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
"layouts/v3-wayfinding",
|
||||
"layouts/api-layout",
|
||||
"layouts/api-security-schemes",
|
||||
"layouts/api-hugo-native";
|
||||
"layouts/api-operations";
|
||||
|
||||
// Import Components
|
||||
@import "components/influxdb-version-detector",
|
||||
|
|
|
|||
|
|
@ -142,24 +142,37 @@ Simplified Cypress tests now that we use standard HTML instead of shadow DOM.
|
|||
|
||||
***
|
||||
|
||||
### Task 6: Clean up styles
|
||||
### Task 6: Clean up styles ✅ COMPLETED
|
||||
|
||||
**Priority:** Medium
|
||||
**Priority:** Medium | **Status:** Completed 2026-02-13
|
||||
|
||||
Remove RapiDoc-specific styles and consolidate Hugo-native styles.
|
||||
Remove RapiDoc-specific styles, JavaScript, and references from the codebase.
|
||||
|
||||
**Files to review:**
|
||||
**Files modified:**
|
||||
|
||||
- `assets/styles/layouts/_api-layout.scss`
|
||||
- `assets/styles/layouts/_api-overrides.scss`
|
||||
- `assets/styles/layouts/_api-hugo-native.scss`
|
||||
- `assets/styles/layouts/_api-layout.scss` - Removed \~40 lines of `rapi-doc::part()` CSS selectors
|
||||
- `assets/styles/layouts/_api-overrides.scss` - Updated comment header
|
||||
- `assets/styles/layouts/_api-security-schemes.scss` - Removed \~290 lines of dead auth modal styles
|
||||
- `assets/js/main.js` - Removed dead `api-auth-input` import and registration
|
||||
- `assets/js/components/api-toc.ts` - Removed RapiDoc-specific code and updated comments
|
||||
|
||||
**Files deleted:**
|
||||
|
||||
- `static/css/rapidoc-custom.css` - Unused static CSS file
|
||||
|
||||
**Changes:**
|
||||
|
||||
1. Remove RapiDoc-specific CSS variables and selectors
|
||||
2. Merge `_api-hugo-native.scss` into `_api-layout.scss`
|
||||
3. Remove `_api-overrides.scss` if only contains RapiDoc overrides
|
||||
4. Update SCSS imports in the main stylesheet
|
||||
1. ✅ Removed `rapi-doc` container styling and `::part()` selectors from `_api-layout.scss`
|
||||
2. ✅ Removed dead auth modal section from `_api-security-schemes.scss` (was for RapiDoc "Try it" integration)
|
||||
3. ✅ Removed `api-auth-input` dead import from `main.js` (component file was already deleted)
|
||||
4. ✅ Removed `setupRapiDocNavigation()` dead function and references from `api-toc.ts`
|
||||
5. ✅ Updated comments throughout to remove RapiDoc mentions
|
||||
6. ✅ Rebuilt `api-docs/scripts/dist/` to update compiled JavaScript
|
||||
|
||||
**Architecture decision:** Kept operation styles separate from layout styles for cleaner separation of concerns:
|
||||
|
||||
- `_api-layout.scss` handles page structure and navigation
|
||||
- `_api-operations.scss` handles operation/schema component rendering (renamed from `_api-hugo-native.scss`)
|
||||
|
||||
***
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
/* Custom RapiDoc overrides */
|
||||
|
||||
/* Reduce parameter table indentation - target the expand column */
|
||||
.m-table tr > td:first-child {
|
||||
width: 0 !important;
|
||||
min-width: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* Reduce param-name cell indentation */
|
||||
.param-name {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
/* Make auth section scrollable on narrow viewports */
|
||||
.security-info-button {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
Loading…
Reference in New Issue