fix(api): Remove duplicated Authentication section

worktree-2025-12-30T19-16-55
Jason Stirnaman 2025-12-29 12:19:03 -06:00
parent 7b34cf1855
commit 0ab9a15a5c
4 changed files with 185 additions and 26 deletions

View File

@ -293,6 +293,127 @@ function createRapiDocElement(
return element;
}
/**
* Inject custom styles into RapiDoc's shadow DOM
* Removes the top border and reduces whitespace above operations
*/
function injectShadowStyles(element: HTMLElement): void {
const tryInject = (): boolean => {
const shadowRoot = (element as unknown as { shadowRoot: ShadowRoot | null })
.shadowRoot;
if (!shadowRoot) return false;
// Check if styles already injected
if (shadowRoot.querySelector('#rapidoc-custom-styles')) return true;
const style = document.createElement('style');
style.id = 'rapidoc-custom-styles';
style.textContent = `
/* Hide the operation divider line */
.divider[part="operation-divider"] {
display: none !important;
}
/* Reduce spacing above operation sections */
.section-gap {
padding-top: 0 !important;
}
/* Hide RapiDoc's built-in security section - we show our own */
/* Target the authorization requirements shown near each operation */
.api-key,
.api-key-info,
.security-info-button,
[class*="api-key"],
[class*="security-info"],
.m-markdown-small:has(.lock-icon),
div:has(> .lock-icon),
/* Target the section showing "AUTHORIZATIONS:" or similar */
.req-resp-container > div:first-child:has(svg[style*="lock"]),
/* Target lock icons and their parent containers */
svg.lock-icon,
.lock-icon,
/* Wide selectors for security-related elements */
[part="section-operation-security"],
.expanded-endpoint-body > div:first-child:has([class*="lock"]) {
display: none !important;
}
`;
shadowRoot.appendChild(style);
// Hide security badge elements by examining content
const hideSecurityBadge = () => {
// Find elements containing security-related text and hide their container
const allElements = shadowRoot.querySelectorAll('span, div');
allElements.forEach((el) => {
const text = el.textContent?.trim();
// Find leaf elements that contain authorization-related text
if (
el.children.length === 0 &&
(text === 'HTTP Bearer' ||
text === 'Bearer' ||
text === 'AUTHORIZATIONS:' ||
text === 'Authorization' ||
text === 'api_token' ||
text === 'BearerAuthentication')
) {
// Walk up the DOM to find a suitable container to hide
// This hides both the text AND any sibling icons (like lock)
let target: HTMLElement = el as HTMLElement;
let parent: HTMLElement | null = el.parentElement;
let depth = 0;
while (parent && depth < 4) {
// Stop at reasonable container boundaries
if (
parent.classList.contains('expanded-endpoint-body') ||
parent.classList.contains('req-resp-container') ||
parent.tagName === 'SECTION'
) {
break;
}
target = parent;
parent = parent.parentElement;
depth++;
}
target.style.display = 'none';
}
});
};
// Run immediately and after delays for dynamic content
hideSecurityBadge();
setTimeout(hideSecurityBadge, 300);
setTimeout(hideSecurityBadge, 800);
// Watch for dynamically added security elements
const observer = new MutationObserver(() => {
hideSecurityBadge();
});
observer.observe(shadowRoot, {
childList: true,
subtree: true,
});
// Disconnect after 5 seconds to avoid performance issues
setTimeout(() => observer.disconnect(), 5000);
return true;
};
// Try immediately
if (tryInject()) return;
// Retry a few times as shadow DOM may not be ready
let attempts = 0;
const maxAttempts = 10;
const interval = setInterval(() => {
attempts++;
if (tryInject() || attempts >= maxAttempts) {
clearInterval(interval);
}
}, 100);
}
/**
* Watch for theme changes and update RapiDoc element
*/
@ -398,6 +519,9 @@ export default async function RapiDocMini({
const rapiDocElement = createRapiDocElement(specUrl, matchPaths, title);
component.appendChild(rapiDocElement);
// Inject custom styles into shadow DOM to remove borders/spacing
injectShadowStyles(rapiDocElement);
// Watch for theme changes and return cleanup function
return watchThemeChanges(component);
} catch (error) {

View File

@ -48,7 +48,8 @@
margin: 0;
code {
background: $g3-castle;
background: $article-code-bg;
color: $article-code;
padding: 0.2em 0.5em;
border-radius: 3px;
font-size: 0.9em;
@ -78,10 +79,26 @@
// Positioned above RapiDoc, integrates with "Try it" via JavaScript API.
////////////////////////////////////////////////////////////////////////////////
.api-auth-row {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.5rem;
}
.api-auth-trigger-wrapper {
position: relative;
display: inline-block;
margin-bottom: 1rem;
}
.api-auth-schemes {
font-size: 0.85rem;
color: $g9-mountain;
.api-auth-label {
font-weight: 400;
opacity: 0.8;
}
}
.api-auth-trigger {
@ -340,6 +357,10 @@
// Dark theme overrides
[data-theme="dark"],
html:has(link[title="dark-theme"]:not([disabled])) {
.api-auth-schemes {
color: $g15-platinum;
}
.api-security-schemes {
border-top-color: $grey25;
@ -352,10 +373,6 @@ html:has(link[title="dark-theme"]:not([disabled])) {
dt {
color: $g15-platinum;
}
dd code {
background: $grey20;
}
}
.scheme-description {

View File

@ -87,8 +87,10 @@
{{ end }}
</section>
{{/* Security Schemes from OpenAPI spec */}}
{{/* Security Schemes from OpenAPI spec (only show if showSecuritySchemes: true) */}}
{{ if .Params.showSecuritySchemes }}
{{ partial "api/security-schemes.html" . }}
{{ end }}
{{ else }}
{{/* Operation Page - RapiDoc with custom slots */}}

View File

@ -41,25 +41,41 @@
<link rel="alternate" type="application/json" href="{{ $specPathJSON | absURL }}" title="OpenAPI Specification (JSON)" />
{{ end }}
{{/* Auth credentials popover trigger - positioned above the operation */}}
<div class="api-auth-trigger-wrapper">
<button type="button"
class="api-auth-trigger btn btn-sm"
data-component="api-auth-input"
data-schemes="{{ $authSchemes }}"
data-popover="true"
aria-expanded="false"
aria-haspopup="dialog">
<svg class="auth-icon" width="14" height="14" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
<path d="M8 1a3.5 3.5 0 0 0-3.5 3.5V6H3a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1h-1.5V4.5A3.5 3.5 0 0 0 8 1zm2 5H6V4.5a2 2 0 1 1 4 0V6z"/>
</svg>
<span class="auth-trigger-text">Set credentials</span>
<span class="auth-status-indicator" hidden aria-label="Credentials configured"></span>
</button>
{{/* Popover container - positioned by CSS */}}
<div class="api-auth-popover" role="dialog" aria-label="API credentials" hidden>
{{/* Content rendered by TypeScript component */}}
{{/* Auth credentials popover trigger with scheme indicator */}}
{{/* Map scheme codes to display names */}}
{{ $schemeNames := dict "bearer" "HTTP Bearer" "token" "API Token" "basic" "HTTP Basic" "querystring" "Query String" }}
{{ $schemeList := split $authSchemes "," }}
{{ $displaySchemes := slice }}
{{ range $schemeList }}
{{ $name := index $schemeNames . }}
{{ if $name }}
{{ $displaySchemes = $displaySchemes | append $name }}
{{ end }}
{{ end }}
<div class="api-auth-row">
<div class="api-auth-trigger-wrapper">
<button type="button"
class="api-auth-trigger btn btn-sm"
data-component="api-auth-input"
data-schemes="{{ $authSchemes }}"
data-popover="true"
aria-expanded="false"
aria-haspopup="dialog">
<svg class="auth-icon" width="14" height="14" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
<path d="M8 1a3.5 3.5 0 0 0-3.5 3.5V6H3a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1h-1.5V4.5A3.5 3.5 0 0 0 8 1zm2 5H6V4.5a2 2 0 1 1 4 0V6z"/>
</svg>
<span class="auth-trigger-text">Set credentials</span>
<span class="auth-status-indicator" hidden aria-label="Credentials configured"></span>
</button>
{{/* Popover container - positioned by CSS */}}
<div class="api-auth-popover" role="dialog" aria-label="API credentials" hidden>
{{/* Content rendered by TypeScript component */}}
</div>
</div>
<span class="api-auth-schemes" title="Supported authentication methods">
<span class="api-auth-label">Auth:</span> {{ delimit $displaySchemes " or " }}
</span>
</div>
{{/* Component container - TypeScript handles initialization */}}
@ -95,7 +111,7 @@
.api-reference-mini {
width: 100%;
margin: 1rem 0;
margin: 0;
position: relative; /* Contains absolutely positioned auth elements */
}