rapidoc
Version:
RapiDoc - Open API spec viewer with built in console
347 lines (320 loc) • 11.7 kB
JavaScript
import { css, LitElement } from 'lit';
import { marked } from 'marked';
import Prism from 'prismjs';
import 'prismjs/components/prism-css';
import 'prismjs/components/prism-yaml';
import 'prismjs/components/prism-go';
import 'prismjs/components/prism-java';
import 'prismjs/components/prism-json';
import 'prismjs/components/prism-bash';
import 'prismjs/components/prism-python';
import 'prismjs/components/prism-http';
import 'prismjs/components/prism-csharp';
// Styles
import FontStyles from '~/styles/font-styles';
import InputStyles from '~/styles/input-styles';
import FlexStyles from '~/styles/flex-styles';
import TableStyles from '~/styles/table-styles';
import PrismStyles from '~/styles/prism-styles';
import TabStyles from '~/styles/tab-styles';
import NavStyles from '~/styles/nav-styles';
import InfoStyles from '~/styles/info-styles';
import EndpointStyles from '~/styles/endpoint-styles';
import ProcessSpec from '~/utils/spec-parser';
import jsonSchemaViewerTemplate from '~/templates/json-schema-viewer-template';
export default class JsonSchemaViewer extends LitElement {
constructor() {
super();
this.isMini = false;
this.updateRoute = 'false';
this.renderStyle = 'focused';
this.showHeader = 'true';
this.allowAdvancedSearch = 'false';
this.selectedExampleForEachSchema = {};
}
static get properties() {
return {
// Spec
specUrl: { type: String, attribute: 'spec-url' },
// Schema Styles
schemaStyle: { type: String, attribute: 'schema-style' },
schemaExpandLevel: { type: Number, attribute: 'schema-expand-level' },
schemaDescriptionExpanded: { type: String, attribute: 'schema-description-expanded' },
allowSchemaDescriptionExpandToggle: { type: String, attribute: 'allow-schema-description-expand-toggle' },
// Hide/show Sections
showHeader: { type: String, attribute: 'show-header' },
showSideNav: { type: String, attribute: 'show-side-nav' },
showInfo: { type: String, attribute: 'show-info' },
// Allow or restrict features
allowSpecUrlLoad: { type: String, attribute: 'allow-spec-url-load' },
allowSpecFileLoad: { type: String, attribute: 'allow-spec-file-load' },
allowSpecFileDownload: { type: String, attribute: 'allow-spec-file-download' },
allowSearch: { type: String, attribute: 'allow-search' },
// Main Colors and Font
theme: { type: String },
bgColor: { type: String, attribute: 'bg-color' },
textColor: { type: String, attribute: 'text-color' },
primaryColor: { type: String, attribute: 'primary-color' },
fontSize: { type: String, attribute: 'font-size' },
regularFont: { type: String, attribute: 'regular-font' },
monoFont: { type: String, attribute: 'mono-font' },
loadFonts: { type: String, attribute: 'load-fonts' },
// Internal Properties
loading: { type: Boolean }, // indicates spec is being loaded
};
}
static get styles() {
return [
FontStyles,
InputStyles,
FlexStyles,
TableStyles,
EndpointStyles,
PrismStyles,
TabStyles,
NavStyles,
InfoStyles,
css`
:host {
all: initial;
display:flex;
flex-direction: column;
min-width:360px;
width:100%;
height:100%;
margin:0;
padding:0;
overflow: hidden;
letter-spacing:normal;
color:var(--fg);
background-color:var(--bg);
font-family:var(--font-regular);
container-type: inline-size;
}
.body {
display:flex;
height:100%;
width:100%;
overflow:hidden;
}
.nav-bar {
width: 230px;
display:flex;
}
.main-content {
margin:0;
padding: 16px;
display:block;
flex:1;
height:100%;
overflow-y: auto;
overflow-x: hidden;
scrollbar-width: thin;
scrollbar-color: var(--border-color) transparent;
}
.main-content-inner--view-mode {
padding: 0 8px;
}
.main-content::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.main-content::-webkit-scrollbar-track {
background:transparent;
}
.main-content::-webkit-scrollbar-thumb {
background-color: var(--border-color);
}
.main-header {
background-color:var(--header-bg);
color:var(--header-fg);
width:100%;
}
.header-title {
font-size:calc(var(--font-size-regular) + 8px);
padding:0 8px;
}
input.header-input{
background:var(--header-color-darker);
color:var(--header-fg);
border:1px solid var(--header-color-border);
flex:1;
padding-right:24px;
border-radius:3px;
}
input.header-input::placeholder {
opacity:0.4;
}
.loader {
margin: 16px auto 16px auto;
border: 4px solid var(--bg3);
border-radius: 50%;
border-top: 4px solid var(--primary-color);
width: 36px;
height: 36px;
animation: spin 2s linear infinite;
}
@container (min-width: 768px) {
.only-large-screen{
display:block;
}
.only-large-screen-flex {
display:flex;
}
}`,
];
}
// Startup
connectedCallback() {
super.connectedCallback();
const parent = this.parentElement;
if (parent) {
if (parent.offsetWidth === 0 && parent.style.width === '') {
parent.style.width = '100vw';
}
if (parent.offsetHeight === 0 && parent.style.height === '') {
parent.style.height = '100vh';
}
if (parent.tagName === 'BODY') {
if (!parent.style.marginTop) { parent.style.marginTop = '0'; }
if (!parent.style.marginRight) { parent.style.marginRight = '0'; }
if (!parent.style.marginBottom) { parent.style.marginBottom = '0'; }
if (!parent.style.marginLeft) { parent.style.marginLeft = '0'; }
}
}
if (this.loadFonts !== 'false') {
const fontDescriptor = {
family: 'Open Sans',
style: 'normal',
weight: '300',
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD',
};
const fontWeight300 = new FontFace(
'Open Sans',
"url(https://fonts.gstatic.com/s/opensans/v18/mem5YaGs126MiZpBA-UN_r8OUuhpKKSTjw.woff2) format('woff2')",
fontDescriptor,
);
fontDescriptor.weight = '600';
const fontWeight600 = new FontFace(
'Open Sans',
"url(https://fonts.gstatic.com/s/opensans/v18/mem5YaGs126MiZpBA-UNirkOUuhpKKSTjw.woff2) format('woff2')",
fontDescriptor,
);
fontWeight300.load().then((font) => { document.fonts.add(font); });
fontWeight600.load().then((font) => { document.fonts.add(font); });
}
this.renderStyle = 'focused';
this.pathsExpanded = this.pathsExpanded === 'true';
if (!this.showInfo || !'true, false,'.includes(`${this.showInfo},`)) { this.showInfo = 'true'; }
if (!this.showSideNav || !'true false'.includes(this.showSideNav)) { this.showSideNav = 'true'; }
if (!this.showHeader || !'true, false,'.includes(`${this.showHeader},`)) { this.showHeader = 'true'; }
if (!this.schemaStyle || !'tree, table,'.includes(`${this.schemaStyle},`)) { this.schemaStyle = 'tree'; }
if (!this.theme || !'light, dark,'.includes(`${this.theme},`)) {
this.theme = (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) ? 'light' : 'dark';
}
if (!this.allowSearch || !'true, false,'.includes(`${this.allowSearch},`)) { this.allowSearch = 'true'; }
if (!this.schemaExpandLevel || this.schemaExpandLevel < 1) { this.schemaExpandLevel = 99999; }
if (!this.schemaDescriptionExpanded || !'true, false,'.includes(`${this.schemaDescriptionExpanded},`)) { this.schemaDescriptionExpanded = 'false'; }
if (!this.fontSize || !'default, large, largest,'.includes(`${this.fontSize},`)) { this.fontSize = 'default'; }
if (!this.matchType || !'includes regex'.includes(this.matchType)) { this.matchType = 'includes'; }
if (!this.allowSchemaDescriptionExpandToggle || !'true, false,'.includes(`${this.allowSchemaDescriptionExpandToggle},`)) { this.allowSchemaDescriptionExpandToggle = 'true'; }
marked.setOptions({
highlight: (code, lang) => {
if (Prism.languages[lang]) {
return Prism.highlight(code, Prism.languages[lang], lang);
}
return code;
},
});
}
render() {
return jsonSchemaViewerTemplate.call(this, true, false, false, this.pathsExpanded);
}
attributeChangedCallback(name, oldVal, newVal) {
if (name === 'spec-url') {
if (oldVal !== newVal) {
// put it at the end of event-loop to load all the attributes
window.setTimeout(async () => {
await this.loadSpec(newVal);
}, 0);
}
}
super.attributeChangedCallback(name, oldVal, newVal);
}
onSpecUrlChange() {
this.setAttribute('spec-url', this.shadowRoot.getElementById('spec-url').value);
}
onSearchChange(e) {
// Todo: Filter Search
this.matchPaths = e.target.value;
}
// Public Method
async loadSpec(specUrl) {
if (!specUrl) {
return;
}
try {
this.resolvedSpec = {
specLoadError: false,
isSpecLoading: true,
tags: [],
};
this.loading = true;
this.loadFailed = false;
this.requestUpdate();
const spec = await ProcessSpec.call(
this,
specUrl,
this.generateMissingTags === 'true',
this.sortTags === 'true',
this.sortSchemas === 'true',
this.getAttribute('sort-endpoints-by'),
this.getAttribute('match-paths'),
this.getAttribute('match-type'),
this.getAttribute('remove-endpoints-with-badge-label-as'),
);
this.loading = false;
this.afterSpecParsedAndValidated(spec);
} catch (err) {
this.loading = false;
this.loadFailed = true;
this.resolvedSpec = null;
console.error(`RapiDoc: Unable to resolve the API spec.. ${err.message}`); // eslint-disable-line no-console
}
}
async afterSpecParsedAndValidated(spec) {
this.resolvedSpec = spec;
const specLoadedEvent = new CustomEvent('spec-loaded', { detail: spec });
this.dispatchEvent(specLoadedEvent);
}
// Called by anchor tags created using markdown
handleHref(e) {
if (e.target.tagName.toLowerCase() === 'a') {
if (e.target.getAttribute('href').startsWith('#')) {
const gotoEl = this.shadowRoot.getElementById(e.target.getAttribute('href').replace('#', ''));
if (gotoEl) {
gotoEl.scrollIntoView({ behavior: 'auto', block: 'start' });
}
}
}
}
// Example Dropdown @change Handler
onSelectExample(e) {
const exampleContainerEl = e.target.closest('.json-schema-example-panel');
const exampleEls = [...exampleContainerEl.querySelectorAll('.example')];
exampleEls.forEach((v) => {
v.style.display = v.dataset.example === e.target.value ? 'flex' : 'none';
});
}
async scrollToEventTarget(event) {
const navEl = event.currentTarget;
if (!navEl.dataset.contentId) {
return;
}
const contentEl = this.shadowRoot.getElementById(navEl.dataset.contentId);
if (contentEl) {
contentEl.scrollIntoView({ behavior: 'auto', block: 'start' });
}
}
}
customElements.define('json-schema-viewer', JsonSchemaViewer);