rapidoc
Version:
RapiDoc - Open API spec viewer with built in console
252 lines (240 loc) • 12.5 kB
JavaScript
import { html } from 'lit';
import { unsafeHTML } from 'lit/directives/unsafe-html.js'; // eslint-disable-line import/extensions
import { marked } from 'marked';
import '~/components/api-request';
import '~/components/api-response';
import codeSamplesTemplate from '~/templates/code-samples-template';
import callbackTemplate from '~/templates/callback-template';
import { pathSecurityTemplate } from '~/templates/security-scheme-template';
import { getMatchedPaths, rapidocApiKey } from '~/utils/common-utils';
function toggleExpand(path) {
if (path.expanded) {
path.expanded = false; // collapse
if (this.updateRoute === 'true') {
this.replaceHistoryState('');
}
} else {
path.expanded = true; // Expand
if (this.updateRoute === 'true') {
const newHash = `${this.routePrefix || '#'}${path.elementId}`;
if (window.location.hash !== newHash) {
this.replaceHistoryState(path.elementId);
}
}
}
this.requestUpdate();
}
export function expandCollapseAll(operationsRootEl, action = 'expand-all') {
const elList = [...operationsRootEl.querySelectorAll('.section-tag')];
if (action === 'expand-all') {
elList.map((el) => {
el.classList.replace('collapsed', 'expanded');
});
} else {
elList.map((el) => {
el.classList.replace('expanded', 'collapsed');
});
}
}
function onExpandCollapseAll(e, action = 'expand-all') {
expandCollapseAll.call(this, e.target.closest('.operations-root'), action);
}
/* eslint-disable indent */
function endpointHeadTemplate(path, pathsExpanded = false) {
return html`
<summary @click="${(e) => { toggleExpand.call(this, path, e); }}" part="section-endpoint-head-${path.expanded ? 'expanded' : 'collapsed'}" class='endpoint-head ${path.method} ${path.deprecated ? 'deprecated' : ''} ${pathsExpanded || path.expanded ? 'expanded' : 'collapsed'}'>
<div part="section-endpoint-head-method" class="method ${path.method} ${path.deprecated ? 'deprecated' : ''}"> ${path.method} </div>
<div part="section-endpoint-head-path" class="path ${path.deprecated ? 'deprecated' : ''}">
${path.path}
${path.isWebhook ? html`<span style="font-family: var(--font-regular); font-size: var(--); font-size: var(--font-size-small); color:var(--primary-color); margin-left: 16px"> Webhook</span>` : ''}
</div>
${path.deprecated
? html`
<span style="font-size:var(--font-size-small); text-transform:uppercase; font-weight:bold; color:var(--red); margin:2px 0 0 5px;">
deprecated
</span>`
: ''
}
${this.showSummaryWhenCollapsed
? html`
<div class="only-large-screen" style="min-width:60px; flex:1"></div>
<div part="section-endpoint-head-description" class="descr">${path.summary || path.shortSummary} </div>`
: ''
}
</summary>
`;
}
function endpointBodyTemplate(path) {
const acceptContentTypes = new Set();
for (const respStatus in path.responses) {
for (const acceptContentType in (path.responses[respStatus]?.content)) {
acceptContentTypes.add(acceptContentType.trim());
}
}
const accept = [...acceptContentTypes].join(', ');
// Filter API Keys that are non-empty and are applicable to the the path
const nonEmptyApiKeys = this.resolvedSpec.securitySchemes.filter((v) => (v.finalKeyValue && path.security?.some((ps) => (v.securitySchemeId in ps)))) || [];
// If a RapiDoc API Key is specified on the element and its value is not hyphen(-) then include it for all paths
const rapiDocApiKey = this.resolvedSpec.securitySchemes.find((v) => (v.securitySchemeId === rapidocApiKey && v.value !== '-'));
if (rapiDocApiKey) {
nonEmptyApiKeys.push(rapiDocApiKey);
}
const codeSampleTabPanel = path.xCodeSamples ? codeSamplesTemplate(path.xCodeSamples) : '';
return html`
<div part="section-endpoint-body-${path.expanded ? 'expanded' : 'collapsed'}" class='endpoint-body ${path.method} ${path.deprecated ? 'deprecated' : ''}'>
<div class="summary">
${path.summary
? html`<div class="title" part="section-endpoint-body-title">${path.summary}</div>`
: path.shortSummary !== path.description
? html`<div class="title" part="section-endpoint-body-title">${path.shortSummary}</div>`
: ''
}
${path.xBadges && path.xBadges?.length > 0
? html`
<div style="display:flex; flex-wrap:wrap;font-size: var(--font-size-small);">
${path.xBadges.map((v) => (
v.color === 'none'
? ''
: html`<span part="endpoint-badge" style="margin:1px; margin-right:5px; padding:1px 8px; font-weight:bold; border-radius:12px; background-color: var(--light-${v.color}, var(--input-bg)); color:var(--${v.color}); border:1px solid var(--${v.color})">${v.label}</span>`
))
}
</div>
`
: ''
}
${path.description ? html`<div part="section-endpoint-body-description" class="m-markdown"> ${unsafeHTML(marked(path.description))}</div>` : ''}
${path.externalDocs?.url || path.externalDocs?.description
? html`<div style="background-color:var(--bg3); padding:2px 8px 8px 8px; margin:8px 0; border-radius:var(--border-radius)">
<div class="m-markdown"> ${unsafeHTML(marked(path.externalDocs?.description || ''))} </div>
${path.externalDocs?.url
? html`<a style="font-family:var(--font-mono); font-size:var(--font-size-small)" href="${path.externalDocs?.url}" target="_blank">
${path.externalDocs?.url} <div style="transform: rotate(270deg) scale(1.5); display: inline-block; margin-left:5px">⇲</div>
</a>`
: ''
}
</div>`
: ''
}
<slot name="${path.elementId}"></slot>
${pathSecurityTemplate.call(this, path.security)}
${codeSampleTabPanel}
</div>
<div class='req-resp-container'>
<div style="display:flex; flex-direction:column" class="view-mode-request ${this.layout}-layout">
<api-request
class = "${this.renderStyle}-mode ${this.layout}-layout"
style = "width:100%;"
webhook = "${path.isWebhook}"
method = "${path.method}"
path = "${path.path}"
.security = "${path.security}"
.parameters = "${path.parameters}"
.request_body = "${path.requestBody}"
.api_keys = "${nonEmptyApiKeys}"
.servers = "${path.servers}"
server-url = "${path.servers && path.servers.length > 0 ? path.servers[0].url : this.selectedServer?.computedUrl}"
active-schema-tab = "${this.defaultSchemaTab}"
fill-request-fields-with-example = "${this.fillRequestFieldsWithExample}"
allow-try = "${this.allowTry}"
show-curl-before-try = "${this.showCurlBeforeTry}"
accept = "${accept}"
render-style="${this.renderStyle}"
schema-style = "${this.schemaStyle}"
schema-expand-level = "${this.schemaExpandLevel}"
schema-description-expanded = "${this.schemaDescriptionExpanded}"
allow-schema-description-expand-toggle = "${this.allowSchemaDescriptionExpandToggle}"
schema-hide-read-only = "${this.schemaHideReadOnly === 'never' ? 'false' : path.isWebhook ? 'false' : 'true'}"
schema-hide-write-only = "${this.schemaHideWriteOnly === 'never' ? 'false' : path.isWebhook ? 'true' : 'false'}"
fetch-credentials = "${this.fetchCredentials}"
exportparts = "wrap-request-btn:wrap-request-btn, btn:btn, btn-fill:btn-fill, btn-outline:btn-outline, btn-try:btn-try, btn-clear:btn-clear, btn-clear-resp:btn-clear-resp,
tab-panel:tab-panel, tab-btn:tab-btn, tab-btn-row:tab-btn-row, tab-coontent:tab-content,
file-input:file-input, textbox:textbox, textbox-param:textbox-param, textarea:textarea, textarea-param:textarea-param,
anchor:anchor, anchor-param-example:anchor-param-example, schema-description:schema-description, schema-multiline-toggle:schema-multiline-toggle"
> </api-request>
${path.callbacks ? callbackTemplate.call(this, path.callbacks) : ''}
</div>
<api-response
class = "${this.renderStyle}-mode"
style = "width:100%;"
webhook = "${path.isWebhook}"
.responses="${path.responses}"
active-schema-tab = "${this.defaultSchemaTab}"
render-style="${this.renderStyle}"
schema-style="${this.schemaStyle}"
schema-expand-level = "${this.schemaExpandLevel}"
schema-description-expanded = "${this.schemaDescriptionExpanded}"
allow-schema-description-expand-toggle = "${this.allowSchemaDescriptionExpandToggle}"
schema-hide-read-only = "${this.schemaHideReadOnly === 'never' ? 'false' : path.isWebhook ? 'true' : 'false'}"
schema-hide-write-only = "${this.schemaHideWriteOnly === 'never' ? 'false' : path.isWebhook ? 'false' : 'true'}"
selected-status = "${Object.keys(path.responses || {})[0] || ''}"
exportparts = "btn:btn, btn-fill:btn-fill, btn-outline:btn-outline, btn-try:btn-try, file-input:file-input,
textbox:textbox, textbox-param:textbox-param, textarea:textarea, textarea-param:textarea-param, anchor:anchor, anchor-param-example:anchor-param-example, btn-clear-resp:btn-clear-resp,
tab-panel:tab-panel, tab-btn:tab-btn, tab-btn-row:tab-btn-row, tab-coontent:tab-content,
schema-description:schema-description, schema-multiline-toggle:schema-multiline-toggle"
> </api-response>
</div>
</div>`;
}
export default function endpointTemplate(isMini = false, pathsExpanded = false) {
if (!this.resolvedSpec) { return ''; }
return html`
${isMini
? ''
: html`
<div style="display:flex; justify-content:flex-end;">
<span @click="${(e) => onExpandCollapseAll(e, 'expand-all')}" style="color:var(--primary-color); cursor:pointer;">
Expand all
</span>
|
<span @click="${(e) => onExpandCollapseAll(e, 'collapse-all')}" style="color:var(--primary-color); cursor:pointer;" >
Collapse all
</span>
sections
</div>`
}
${this.resolvedSpec.tags.map((tag) => html`
${isMini
? html`
<div class='section-tag-body'>
${tag.paths.filter((path) => {
if (this.searchVal) {
return getMatchedPaths(this.searchVal, path, tag.name);
}
return true;
}).map((path) => html`
<section id='${path.elementId}' class='m-endpoint regular-font ${path.method} ${pathsExpanded || path.expanded ? 'expanded' : 'collapsed'}'>
${endpointHeadTemplate.call(this, path, pathsExpanded)}
${pathsExpanded || path.expanded ? endpointBodyTemplate.call(this, path) : ''}
</section>`)
}
</div>
`
: html`
<div class='regular-font section-gap section-tag ${tag.expanded ? 'expanded' : 'collapsed'}'>
<div class='section-tag-header' @click="${() => { tag.expanded = !tag.expanded; this.requestUpdate(); }}">
<div id='${tag.elementId}' class="sub-title tag" style="color:var(--primary-color)">${tag.displayName || tag.name}</div>
</div>
<div class='section-tag-body'>
<slot name="${tag.elementId}"></slot>
<div class="regular-font regular-font-size m-markdown" style="padding-bottom:12px">
${unsafeHTML(marked(tag.description || ''))}
</div>
${tag.paths.filter((v) => {
if (this.searchVal) {
return getMatchedPaths(this.searchVal, v, tag.name);
}
return true;
}).map((path) => html`
<section part="section-endpoint" id='${path.elementId}' class='m-endpoint regular-font ${path.method} ${pathsExpanded || path.expanded ? 'expanded' : 'collapsed'}'>
${endpointHeadTemplate.call(this, path, pathsExpanded)}
${pathsExpanded || path.expanded ? endpointBodyTemplate.call(this, path) : ''}
</section>`)
}
</div>
</div>
`
}
`)
}`;
}
/* eslint-enable indent */