rapidoc
Version:
RapiDoc - Open API spec viewer with built in console
200 lines (190 loc) • 10.9 kB
JavaScript
import { html } from 'lit';
import { unsafeHTML } from 'lit/directives/unsafe-html.js'; // eslint-disable-line import/extensions
import { marked } from 'marked';
import { rapidocApiKey } from '~/utils/common-utils';
import { pathSecurityTemplate } from '~/templates/security-scheme-template';
import codeSamplesTemplate from '~/templates/code-samples-template';
import callbackTemplate from '~/templates/callback-template';
import '~/components/api-request';
import '~/components/api-response';
/* eslint-disable indent */
function headingRenderer(tagElementId) {
const renderer = new marked.Renderer();
renderer.heading = ((text, level, raw, slugger) => `<h${level} class="observe-me" id="${tagElementId}--${slugger.slug(raw)}">${text}</h${level}>`);
return renderer;
}
function expandCollapseTagDescription(e) {
const tagDescriptionEl = e.target.closest('.tag-container').querySelector('.tag-description');
const tagIconEl = e.target.closest('.tag-container').querySelector('.tag-icon');
if (tagDescriptionEl && tagIconEl) {
const isExpanded = tagDescriptionEl.classList.contains('expanded');
if (isExpanded) {
tagDescriptionEl.style.maxHeight = 0;
tagDescriptionEl.classList.replace('expanded', 'collapsed');
tagIconEl.classList.replace('expanded', 'collapsed');
} else {
tagDescriptionEl.style.maxHeight = `${tagDescriptionEl.scrollHeight}px`;
tagDescriptionEl.classList.replace('collapsed', 'expanded');
tagIconEl.classList.replace('collapsed', 'expanded');
}
}
}
export function expandedEndpointBodyTemplate(path, tagName = '', tagDescription = '') {
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.call(this, path.xCodeSamples) : '';
return html`
${this.renderStyle === 'read' ? html`<div class='divider' part="operation-divider"></div>` : ''}
<div class='expanded-endpoint-body observe-me ${path.method} ${path.deprecated ? 'deprecated' : ''} ' part="section-operation ${path.elementId}" id='${path.elementId}'>
${(this.renderStyle === 'focused' && tagName !== 'General ⦂')
? html`
<div class="tag-container" part="section-operation-tag">
<span class="upper" style="font-weight:bold; font-size:18px;"> ${tagName} </span>
${tagDescription
? html`
<svg class="tag-icon collapsed" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" fill="none" style="stroke:var(--primary-color); vertical-align:top; cursor:pointer"
@click="${(e) => { expandCollapseTagDescription.call(this, e); }}"
>
<path d="M12 20h-6a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h8"></path><path d="M18 4v17"></path><path d="M15 18l3 3l3 -3"></path>
</svg>
<div class="tag-description collapsed" style="max-height:0px; overflow:hidden; margin-top:16px; border:1px solid var(--border-color)">
<div class="m-markdown" style="padding:8px"> ${unsafeHTML(marked(tagDescription))}</div>
</div>`
: ''
}
</div>
`
: ''
}
${path.deprecated ? html`<div class="bold-text red-text"> DEPRECATED </div>` : ''}
${html`
${path.xBadges && path.xBadges?.length > 0
? html`
<div style="display:flex; flex-wrap:wrap; margin-bottom: -24px; font-size: var(--font-size-small);">
${path.xBadges.map((v) => (
v.color === 'none'
? ''
: html`<span 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>
`
: ''
}
<h2 part="section-operation-summary"> ${path.shortSummary || `${path.method.toUpperCase()} ${path.path}`}</h2>
${path.isWebhook
? html`<span part="section-operation-webhook" style="color:var(--primary-color); font-weight:bold; font-size: var(--font-size-regular);"> WEBHOOK </span>`
: html`
<div part="section-operation-webhook-method" class="mono-font regular-font-size" style="text-align:left; direction:ltr; padding: 8px 0; color:var(--fg3)">
<span part="label-operation-method" class="regular-font upper method-fg bold-text ${path.method}">${path.method}</span>
<span part="label-operation-path">${path.path}</span>
</div>
`
}
<slot name="${path.elementId}"></slot>`
}
${path.description ? html`<div class="m-markdown"> ${unsafeHTML(marked(path.description))}</div>` : ''}
${pathSecurityTemplate.call(this, path.security)}
${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>`
: ''
}
${codeSampleTabPanel}
<div class='expanded-req-resp-container'>
<api-request
class = "${this.renderStyle}-mode"
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?.[0]?.url || this.selectedServer?.computedUrl}"
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}"
active-schema-tab = "${this.defaultSchemaTab}"
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) : ''}
<api-response
class = "${this.renderStyle}-mode"
style = "width:100%;"
webhook = "${path.isWebhook}"
.responses = "${path.responses}"
render-style = "${this.renderStyle}"
schema-style = "${this.schemaStyle}"
active-schema-tab = "${this.defaultSchemaTab}"
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-response-status:btn-response-status, btn-selected-response-status:btn-selected-response-status, btn-fill:btn-fill, btn-copy:btn-copy,
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 expandedEndpointTemplate() {
if (!this.resolvedSpec) { return ''; }
return html`
${this.resolvedSpec.tags.map((tag) => html`
<section id="${tag.elementId}" part="section-tag" class="regular-font section-gap--read-mode observe-me" style="border-top:1px solid var(--primary-color);">
<div class="title tag" part="section-tag-title label-tag-title">${tag.displayName || tag.name}</div>
<slot name="${tag.elementId}"></slot>
<div class="regular-font-size">
${
unsafeHTML(`
<div class="m-markdown regular-font">
${marked(tag.description || '', this.infoDescriptionHeadingsInNavBar === 'true' ? { renderer: headingRenderer(tag.elementId) } : undefined)}
</div>`)
}
</div>
</section>
<section class="regular-font section-gap--read-mode" part="section-operations-in-tag">
${tag.paths.map((path) => expandedEndpointBodyTemplate.call(this, path))}
</section>
`)
}
`;
}
/* eslint-enable indent */