rapidoc 11.0.0 → 12.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -31
- package/dist/rapidoc-min.js +1424 -213
- package/dist/rapidoc-static-min.js +13542 -0
- package/package.json +48 -34
- package/src/components/api-request.ts +869 -0
- package/src/components/api-response.ts +509 -0
- package/src/components/json-tree.ts +171 -0
- package/src/components/schema-base-element.ts +315 -0
- package/src/components/schema-table.ts +430 -0
- package/src/components/schema-tree.ts +386 -0
- package/src/components/tag-input.ts +184 -0
- package/src/index.js +6 -0
- package/src/oauth-receiver.ts +29 -0
- package/src/rapidoc-mini.ts +582 -0
- package/src/rapidoc.ts +1808 -0
- package/src/static/index.d.ts +149 -0
- package/src/static/index.js +62 -0
- package/src/static/navigation.js +497 -0
- package/src/static/render.js +490 -0
- package/src/static/serializer.js +216 -0
- package/src/styles/auth-styles.js +318 -0
- package/src/styles/border-styles.js +24 -0
- package/src/styles/css-bundle.js +129 -0
- package/src/styles/custom-styles.js +8 -0
- package/src/styles/dialog-box-styles.js +102 -0
- package/src/styles/endpoint-styles.js +559 -0
- package/src/styles/flex-styles.js +16 -0
- package/src/styles/font-styles.js +432 -0
- package/src/styles/info-styles.js +36 -0
- package/src/styles/input-styles.js +555 -0
- package/src/styles/layout-styles.js +396 -0
- package/src/styles/microlighter-styles.js +99 -0
- package/src/styles/nav-styles.js +609 -0
- package/src/styles/param-list-styles.js +261 -0
- package/src/styles/schema-styles.js +288 -0
- package/src/styles/tab-styles.js +106 -0
- package/src/styles/theme-tokens.js +582 -0
- package/src/templates/advanced-search-dialog-template.js +94 -0
- package/src/templates/api-response-template.js +219 -0
- package/src/templates/callback-template.js +86 -0
- package/src/templates/code-samples-template.js +52 -0
- package/src/templates/components-template.js +86 -0
- package/src/templates/downloads-template.js +85 -0
- package/src/templates/endpoint-template.js +386 -0
- package/src/templates/expanded-endpoint-template.js +266 -0
- package/src/templates/focused-endpoint-template.js +145 -0
- package/src/templates/header-template.js +92 -0
- package/src/templates/logo-template.js +21 -0
- package/src/templates/main-body-template.js +102 -0
- package/src/templates/navbar-template.js +489 -0
- package/src/templates/overview-template.js +104 -0
- package/src/templates/request-body-template.js +651 -0
- package/src/templates/request-params-template.js +474 -0
- package/src/templates/security-scheme-template.js +1263 -0
- package/src/templates/server-template.js +152 -0
- package/src/utils/auto-color-scheme.js +99 -0
- package/src/utils/bind-navbar.js +175 -0
- package/src/utils/color-utils.js +59 -0
- package/src/utils/common-utils.js +191 -0
- package/src/utils/curl-utils.js +123 -0
- package/src/utils/highlighter.js +416 -0
- package/src/utils/markdown-utils.js +110 -0
- package/src/utils/mcp/common.js +467 -0
- package/src/utils/mcp/generators/csharp.js +242 -0
- package/src/utils/mcp/generators/go.js +298 -0
- package/src/utils/mcp/generators/java.js +330 -0
- package/src/utils/mcp/generators/python.js +248 -0
- package/src/utils/mcp/generators/rust.js +281 -0
- package/src/utils/mcp/generators/typescript.js +290 -0
- package/src/utils/mcp-generator.js +143 -0
- package/src/utils/mock-interceptor.js +508 -0
- package/src/utils/oauth-utils.js +87 -0
- package/src/utils/request-utils.js +203 -0
- package/src/utils/response-utils.js +203 -0
- package/src/utils/sanitize.js +57 -0
- package/src/utils/schema-utils.js +1927 -0
- package/src/utils/slugger.js +189 -0
- package/src/utils/spec-parser.js +915 -0
- package/src/utils/url-utils.js +211 -0
- package/src/utils/xml-utils.js +61 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders the API server selector dropdown and server variable configuration form.
|
|
3
|
+
*/
|
|
4
|
+
import { html } from "lit";
|
|
5
|
+
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
|
6
|
+
import { sanitizeHTML } from "../utils/sanitize.js";
|
|
7
|
+
import { marked } from "marked";
|
|
8
|
+
|
|
9
|
+
export function setApiServer(serverUrl) {
|
|
10
|
+
const serverObj = this.resolvedSpec?.servers.find((s) => s.url === serverUrl);
|
|
11
|
+
if (!serverObj) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
this.selectedServer = serverObj;
|
|
15
|
+
this.requestUpdate();
|
|
16
|
+
this.dispatchEvent(
|
|
17
|
+
new CustomEvent("api-server-change", {
|
|
18
|
+
bubbles: true,
|
|
19
|
+
composed: true,
|
|
20
|
+
detail: {
|
|
21
|
+
selectedServer: serverObj,
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function onApiServerVarChange(e, serverObj) {
|
|
29
|
+
const inputEls = [...e.currentTarget.closest("table").querySelectorAll("input, select")];
|
|
30
|
+
let tempUrl = serverObj.url;
|
|
31
|
+
inputEls.forEach((v) => {
|
|
32
|
+
const regex = new RegExp(`{${v.dataset.var}}`, "g");
|
|
33
|
+
tempUrl = tempUrl.replace(regex, v.value);
|
|
34
|
+
});
|
|
35
|
+
serverObj.computedUrl = tempUrl;
|
|
36
|
+
this.requestUpdate();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function serverVarsTemplate() {
|
|
40
|
+
// const selectedServerObj = this.resolvedSpec.servers.find((v) => (v.url === this.selectedServer));
|
|
41
|
+
return this.selectedServer && this.selectedServer.variables
|
|
42
|
+
? html`
|
|
43
|
+
<h3 class="rapidoc-heading label-heading table-title" part="heading heading-3">Server variables</h3>
|
|
44
|
+
<div class="param-list-container">
|
|
45
|
+
<ul class="param-list server-vars" part="param-list" role="list">
|
|
46
|
+
${Object.entries(this.selectedServer.variables).map(
|
|
47
|
+
(kv) => html`
|
|
48
|
+
<li class="param" part="param">
|
|
49
|
+
<div class="param-label" part="param-label">
|
|
50
|
+
<div class="param-name" part="param-name">${kv[0]}</div>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="param-input" part="param-input">
|
|
53
|
+
${
|
|
54
|
+
kv[1].enum
|
|
55
|
+
? html` <select
|
|
56
|
+
data-var="${kv[0]}"
|
|
57
|
+
@input=${(e) => {
|
|
58
|
+
onApiServerVarChange.call(this, e, this.selectedServer);
|
|
59
|
+
}}
|
|
60
|
+
>
|
|
61
|
+
${Object.entries(kv[1].enum).map((e) =>
|
|
62
|
+
kv[1].default === e[1]
|
|
63
|
+
? html`<option selected label=${e[1]} value=${e[1]}></option>`
|
|
64
|
+
: html`<option label=${e[1]} value=${e[1]}></option>`,
|
|
65
|
+
)}
|
|
66
|
+
</select>`
|
|
67
|
+
: html` <input
|
|
68
|
+
type="text"
|
|
69
|
+
part="textbox textbox-server-var"
|
|
70
|
+
spellcheck="false"
|
|
71
|
+
data-var="${kv[0]}"
|
|
72
|
+
value="${kv[1].default}"
|
|
73
|
+
@input=${(e) => {
|
|
74
|
+
onApiServerVarChange.call(this, e, this.selectedServer);
|
|
75
|
+
}}
|
|
76
|
+
/>`
|
|
77
|
+
}
|
|
78
|
+
</div>
|
|
79
|
+
${
|
|
80
|
+
kv[1].description
|
|
81
|
+
? html`<div class="param-details" part="param-details">
|
|
82
|
+
<div class="m-markdown-small">${unsafeHTML(sanitizeHTML(marked(kv[1].description)))}</div>
|
|
83
|
+
</div>`
|
|
84
|
+
: ""
|
|
85
|
+
}
|
|
86
|
+
</li>
|
|
87
|
+
`,
|
|
88
|
+
)}
|
|
89
|
+
</ul>
|
|
90
|
+
</div>
|
|
91
|
+
`
|
|
92
|
+
: "";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export default function serverTemplate() {
|
|
96
|
+
if (!this.resolvedSpec || this.resolvedSpec.specLoadError) {
|
|
97
|
+
return "";
|
|
98
|
+
}
|
|
99
|
+
return html`<section
|
|
100
|
+
id="servers"
|
|
101
|
+
part="section-servers"
|
|
102
|
+
style="text-align:left; direction:ltr; margin-top:24px; margin-bottom:24px;"
|
|
103
|
+
class="regular-font observe-me ${"read focused".includes(this.renderStyle) ? "section-gap--read-mode" : "section-gap"}"
|
|
104
|
+
>
|
|
105
|
+
<h2 part="section-servers-title heading heading-2" class="rapidoc-heading sub-title" style="display:flex; align-items:center; gap:8px;">
|
|
106
|
+
API Server
|
|
107
|
+
${
|
|
108
|
+
this.mockServer === "true"
|
|
109
|
+
? html`<span
|
|
110
|
+
style="font-size:var(--font-size-small); font-weight:bold; color:var(--green); border:1px solid var(--green); border-radius:var(--radius); padding:1px 6px; letter-spacing:0.5px;"
|
|
111
|
+
title="In-browser mock server is active"
|
|
112
|
+
>
|
|
113
|
+
MOCK ACTIVE
|
|
114
|
+
</span>`
|
|
115
|
+
: ""
|
|
116
|
+
}
|
|
117
|
+
</h2>
|
|
118
|
+
<div class="mono-font" style="margin: 12px 0; font-size:calc(var(--font-size-small) + 1px);">
|
|
119
|
+
${
|
|
120
|
+
!this.resolvedSpec.servers || this.resolvedSpec.servers?.length === 0
|
|
121
|
+
? ""
|
|
122
|
+
: html`
|
|
123
|
+
${this.resolvedSpec?.servers.map(
|
|
124
|
+
(server, i) => html`
|
|
125
|
+
<div class="server-opt-row">
|
|
126
|
+
<input
|
|
127
|
+
type="radio"
|
|
128
|
+
name="api_server"
|
|
129
|
+
id="srvr-opt-${i}"
|
|
130
|
+
value="${server.url}"
|
|
131
|
+
@change=${() => {
|
|
132
|
+
setApiServer.call(this, server.url);
|
|
133
|
+
}}
|
|
134
|
+
.checked="${this.selectedServer.url === server.url}"
|
|
135
|
+
/>
|
|
136
|
+
<label for="srvr-opt-${i}">
|
|
137
|
+
${server.url} ${server.description ? html`- <span class="regular-font">${server.description} </span>` : ""}
|
|
138
|
+
</label>
|
|
139
|
+
</div>
|
|
140
|
+
`,
|
|
141
|
+
)}
|
|
142
|
+
`
|
|
143
|
+
}
|
|
144
|
+
<div class="table-title primary-text" part="label-selected-server">
|
|
145
|
+
SELECTED: ${this.selectedServer?.computedUrl || "none"}
|
|
146
|
+
${this.mockServer === "true" ? html`<span style="color:var(--green); margin-left:6px;">(Mocked)</span>` : ""}
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
<slot name="servers"></slot>
|
|
150
|
+
${serverVarsTemplate.call(this)}
|
|
151
|
+
</section>`;
|
|
152
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lit reactive controller behind color-scheme="auto" on <rapi-doc> and <rapi-doc-mini>.
|
|
3
|
+
*
|
|
4
|
+
* "auto" follows the host page: it reads the page's CSS `color-scheme` (as set by Starlight and most
|
|
5
|
+
* theme toggles, e.g. `:root[data-theme='light'] { color-scheme: light }`), or the OS preference when
|
|
6
|
+
* the page declares none or both. The resolved 'dark' / 'light' is written to the element's
|
|
7
|
+
* color-scheme property and attribute, which drive the styles, and is re-resolved when attributes
|
|
8
|
+
* on <html> or <body> change (data-theme, class, style) or the OS preference changes.
|
|
9
|
+
*
|
|
10
|
+
* Setting color-scheme to any other value later turns auto mode off.
|
|
11
|
+
*/
|
|
12
|
+
export class AutoColorSchemeController {
|
|
13
|
+
constructor(host) {
|
|
14
|
+
this.host = host;
|
|
15
|
+
this.active = false;
|
|
16
|
+
this.resolved = "";
|
|
17
|
+
this.observer = null;
|
|
18
|
+
this.media = null;
|
|
19
|
+
this.onPageChange = () => this.apply();
|
|
20
|
+
host.addController(this);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
hostConnected() {
|
|
24
|
+
if (this.host.colorScheme === "auto") {
|
|
25
|
+
this.enable();
|
|
26
|
+
} else if (this.active) {
|
|
27
|
+
this.watch(true);
|
|
28
|
+
this.apply();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
hostDisconnected() {
|
|
33
|
+
this.watch(false);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
hostUpdate() {
|
|
37
|
+
const value = this.host.colorScheme;
|
|
38
|
+
if (value === "auto") {
|
|
39
|
+
this.enable();
|
|
40
|
+
} else if (this.active && value !== this.resolved) {
|
|
41
|
+
// An explicit value from the page or a script
|
|
42
|
+
this.active = false;
|
|
43
|
+
this.watch(false);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
enable() {
|
|
48
|
+
this.active = true;
|
|
49
|
+
this.watch(true);
|
|
50
|
+
this.apply();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
apply() {
|
|
54
|
+
if (!this.active || typeof window === "undefined") {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const scheme = resolvePageColorScheme(this.host);
|
|
58
|
+
this.resolved = scheme;
|
|
59
|
+
if (this.host.colorScheme !== scheme) {
|
|
60
|
+
this.host.colorScheme = scheme;
|
|
61
|
+
}
|
|
62
|
+
// Set the attribute now (property reflection is async) so the styles switch immediately.
|
|
63
|
+
if (this.host.getAttribute("color-scheme") !== scheme) {
|
|
64
|
+
this.host.setAttribute("color-scheme", scheme);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
watch(enable) {
|
|
69
|
+
this.observer?.disconnect();
|
|
70
|
+
this.observer = null;
|
|
71
|
+
this.media?.removeEventListener("change", this.onPageChange);
|
|
72
|
+
this.media = null;
|
|
73
|
+
if (!enable || typeof window === "undefined") {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
this.observer = new MutationObserver(this.onPageChange);
|
|
77
|
+
this.observer.observe(document.documentElement, { attributes: true });
|
|
78
|
+
if (document.body) {
|
|
79
|
+
this.observer.observe(document.body, { attributes: true });
|
|
80
|
+
}
|
|
81
|
+
this.media = window.matchMedia("(prefers-color-scheme: light)");
|
|
82
|
+
this.media.addEventListener("change", this.onPageChange);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 'dark' or 'light' for the page around `el`. Reads the parent's CSS color-scheme: the element sets
|
|
88
|
+
* color-scheme on itself from its own attribute.
|
|
89
|
+
*/
|
|
90
|
+
export function resolvePageColorScheme(el) {
|
|
91
|
+
const pageEl = el.parentElement || el.getRootNode().host || document.documentElement;
|
|
92
|
+
const pageScheme = getComputedStyle(pageEl).colorScheme || "";
|
|
93
|
+
const hasDark = /\bdark\b/.test(pageScheme);
|
|
94
|
+
const hasLight = /\blight\b/.test(pageScheme);
|
|
95
|
+
if (hasDark !== hasLight) {
|
|
96
|
+
return hasDark ? "dark" : "light";
|
|
97
|
+
}
|
|
98
|
+
return window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
|
|
99
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import {
|
|
2
|
+
expandCollapseNavBarTag,
|
|
3
|
+
expandCollapseNavBarTagGroup,
|
|
4
|
+
expandParentNavContainers,
|
|
5
|
+
} from "../templates/navbar-template.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Connects an external navigation bar (e.g. in Astro, Next.js, or Starlight portals)
|
|
9
|
+
* to a <rapi-doc> component, reusing RapiDoc's internal accordion mechanics,
|
|
10
|
+
* scroll controller, and active item tracking.
|
|
11
|
+
*
|
|
12
|
+
* @param {HTMLElement|string} sidebar - Sidebar element or selector
|
|
13
|
+
* @param {HTMLElement|string} rapiDoc - <rapi-doc> element or selector
|
|
14
|
+
* @param {object} [options]
|
|
15
|
+
* @param {string} [options.activeClass='active'] - Class applied to active navigation item
|
|
16
|
+
* @param {boolean} [options.updateHistory=true] - Whether to sync window history
|
|
17
|
+
* @returns {() => void} Cleanup unbind function
|
|
18
|
+
*/
|
|
19
|
+
export function bindNavBar(sidebar, rapiDoc, options = {}) {
|
|
20
|
+
const sidebarEl = typeof sidebar === "string" ? document.querySelector(sidebar) : sidebar;
|
|
21
|
+
const rapiDocEl = typeof rapiDoc === "string" ? document.querySelector(rapiDoc) : rapiDoc;
|
|
22
|
+
|
|
23
|
+
if (!sidebarEl || typeof window === "undefined") {
|
|
24
|
+
return () => {};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const activeClass = options.activeClass || "active";
|
|
28
|
+
const updateHistory = options.updateHistory !== false;
|
|
29
|
+
|
|
30
|
+
// 1. Resolve matching navigation link in sidebar
|
|
31
|
+
function findMatchingLink(targetId) {
|
|
32
|
+
if (!targetId) return null;
|
|
33
|
+
return (
|
|
34
|
+
sidebarEl.querySelector(`#link-${CSS.escape(targetId)}`) ||
|
|
35
|
+
sidebarEl.querySelector(`.nav-bar-path[data-content-id="${CSS.escape(targetId)}"]`) ||
|
|
36
|
+
sidebarEl.querySelector(`.nav-bar-path[data-op-slug="${CSS.escape(targetId)}"]`) ||
|
|
37
|
+
sidebarEl.querySelector(`#link-${CSS.escape(targetId.replace(/^[a-z0-9_-]+--/, ""))}`) ||
|
|
38
|
+
(targetId === "overview" ? sidebarEl.querySelector("#link-overview, .nav-bar-info") : null)
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 2. Set active navigation link and auto-expand parent containers
|
|
43
|
+
function setActiveNavId(targetId, shouldUpdateHistory = true) {
|
|
44
|
+
const link = findMatchingLink(targetId);
|
|
45
|
+
if (!link || link.classList.contains(activeClass)) return;
|
|
46
|
+
|
|
47
|
+
sidebarEl.querySelectorAll("." + activeClass).forEach((el) => el.classList.remove(activeClass));
|
|
48
|
+
link.classList.add(activeClass);
|
|
49
|
+
|
|
50
|
+
// Reuse RapiDoc's native hierarchy expander
|
|
51
|
+
expandParentNavContainers(link);
|
|
52
|
+
|
|
53
|
+
if (shouldUpdateHistory && updateHistory) {
|
|
54
|
+
const href = link.getAttribute("href");
|
|
55
|
+
if (href) window.history.replaceState(null, "", href);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Scroll active item into view inside the sidebar container
|
|
59
|
+
const navScroll = link.closest(".nav-scroll");
|
|
60
|
+
if (navScroll) {
|
|
61
|
+
const itemRect = link.getBoundingClientRect();
|
|
62
|
+
const scrollRect = navScroll.getBoundingClientRect();
|
|
63
|
+
if (itemRect.top < scrollRect.top + 20 || itemRect.bottom > scrollRect.bottom - 40) {
|
|
64
|
+
const targetTop = itemRect.top - scrollRect.top + navScroll.scrollTop - 40;
|
|
65
|
+
navScroll.scrollTo({ top: targetTop, behavior: "smooth" });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 3. Delegated Click Listener (reusing RapiDoc's accordion handlers)
|
|
71
|
+
const handleClick = (e) => {
|
|
72
|
+
const navEl =
|
|
73
|
+
e.target.closest("[data-action]") || e.target.closest(".nav-bar-path, .nav-bar-info");
|
|
74
|
+
if (!navEl) return;
|
|
75
|
+
|
|
76
|
+
const action = navEl.dataset?.action;
|
|
77
|
+
if (action === "expand-collapse-tag") {
|
|
78
|
+
expandCollapseNavBarTag(navEl, "toggle");
|
|
79
|
+
} else if (action === "expand-collapse-tag-group") {
|
|
80
|
+
expandCollapseNavBarTagGroup(navEl, "toggle");
|
|
81
|
+
} else if (
|
|
82
|
+
action === "navigate" ||
|
|
83
|
+
navEl.classList.contains("nav-bar-path") ||
|
|
84
|
+
navEl.classList.contains("nav-bar-info")
|
|
85
|
+
) {
|
|
86
|
+
const href = navEl.getAttribute("href") || navEl.getAttribute("data-tag-href");
|
|
87
|
+
const hashIdx = href ? href.indexOf("#") : -1;
|
|
88
|
+
const elementId =
|
|
89
|
+
(hashIdx !== -1 ? href.substring(hashIdx + 1) : href) ||
|
|
90
|
+
navEl.getAttribute("data-content-id") ||
|
|
91
|
+
navEl.getAttribute("data-op-slug") ||
|
|
92
|
+
navEl.id?.replace(/^link-/, "");
|
|
93
|
+
|
|
94
|
+
if (elementId && rapiDocEl && typeof rapiDocEl.scrollToPath === "function") {
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
setActiveNavId(elementId, false);
|
|
97
|
+
rapiDocEl.scrollToPath(elementId);
|
|
98
|
+
if (updateHistory && href) {
|
|
99
|
+
window.history.pushState(null, "", href);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
sidebarEl.addEventListener("click", handleClick);
|
|
105
|
+
|
|
106
|
+
// 4. Sidebar Search Filter
|
|
107
|
+
const filterInput = sidebarEl.querySelector(".portal-filter-input, .nav-bar-search-input");
|
|
108
|
+
let handleFilter = null;
|
|
109
|
+
if (filterInput) {
|
|
110
|
+
handleFilter = (e) => {
|
|
111
|
+
const query = (e.target.value || "").toLowerCase().trim();
|
|
112
|
+
sidebarEl.querySelectorAll(".nav-bar-path, .portal-sidebar-op-item").forEach((item) => {
|
|
113
|
+
item.style.display = item.textContent.toLowerCase().includes(query) ? "flex" : "none";
|
|
114
|
+
});
|
|
115
|
+
sidebarEl.querySelectorAll(".nav-bar-tag-and-paths").forEach((tag) => {
|
|
116
|
+
const hasVisible = tag.querySelector(
|
|
117
|
+
'.nav-bar-path:not([style*="display: none"]), .portal-sidebar-op-item:not([style*="display: none"])',
|
|
118
|
+
);
|
|
119
|
+
tag.style.display = hasVisible || !query ? "" : "none";
|
|
120
|
+
if (query && hasVisible) expandCollapseNavBarTag(tag, "expand");
|
|
121
|
+
});
|
|
122
|
+
sidebarEl.querySelectorAll(".nav-bar-tag-group").forEach((group) => {
|
|
123
|
+
const hasVisible = group.querySelector(
|
|
124
|
+
'.nav-bar-path:not([style*="display: none"]), .portal-sidebar-op-item:not([style*="display: none"])',
|
|
125
|
+
);
|
|
126
|
+
group.style.display = hasVisible || !query ? "" : "none";
|
|
127
|
+
if (query && hasVisible) expandCollapseNavBarTagGroup(group, "expand");
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
filterInput.addEventListener("input", handleFilter);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 5. Native rapi-doc nav-active-change event listener
|
|
134
|
+
const handleNavActiveChange = (e) => {
|
|
135
|
+
const targetId = e.detail?.operationId || e.detail?.id || e.detail?.elementId;
|
|
136
|
+
if (targetId) setActiveNavId(targetId, true);
|
|
137
|
+
};
|
|
138
|
+
if (rapiDocEl) {
|
|
139
|
+
rapiDocEl.addEventListener("nav-active-change", handleNavActiveChange);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 6. Popstate / hash sync
|
|
143
|
+
const handleHashSync = () => {
|
|
144
|
+
if (window.location.hash) {
|
|
145
|
+
const hash = window.location.hash.replace(/^#/, "");
|
|
146
|
+
if (hash) {
|
|
147
|
+
setActiveNavId(hash, false);
|
|
148
|
+
if (rapiDocEl && typeof rapiDocEl.scrollToPath === "function") {
|
|
149
|
+
rapiDocEl.scrollToPath(hash);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
window.addEventListener("popstate", handleHashSync);
|
|
155
|
+
window.addEventListener("hashchange", handleHashSync);
|
|
156
|
+
|
|
157
|
+
// Initial sync on load and spec load
|
|
158
|
+
if (rapiDocEl) {
|
|
159
|
+
rapiDocEl.addEventListener("spec-loaded", () => setTimeout(handleHashSync, 80));
|
|
160
|
+
if (typeof rapiDocEl.updateComplete?.then === "function") {
|
|
161
|
+
rapiDocEl.updateComplete.then(() => setTimeout(handleHashSync, 50));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
setTimeout(handleHashSync, 150);
|
|
165
|
+
|
|
166
|
+
return () => {
|
|
167
|
+
sidebarEl.removeEventListener("click", handleClick);
|
|
168
|
+
if (filterInput && handleFilter) filterInput.removeEventListener("input", handleFilter);
|
|
169
|
+
if (rapiDocEl) rapiDocEl.removeEventListener("nav-active-change", handleNavActiveChange);
|
|
170
|
+
window.removeEventListener("popstate", handleHashSync);
|
|
171
|
+
window.removeEventListener("hashchange", handleHashSync);
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export default bindNavBar;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
color: {
|
|
3
|
+
inputReverseFg: "#fff",
|
|
4
|
+
inputReverseBg: "#333",
|
|
5
|
+
headerBg: "#444",
|
|
6
|
+
getRgb(hex) {
|
|
7
|
+
if (hex.indexOf("#") === 0) {
|
|
8
|
+
hex = hex.slice(1, 7);
|
|
9
|
+
}
|
|
10
|
+
// convert 3-digit hex to 6-digits.
|
|
11
|
+
if (hex.length === 3 || hex.length === 4) {
|
|
12
|
+
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
13
|
+
}
|
|
14
|
+
if (hex.length !== 6) {
|
|
15
|
+
throw new Error("Invalid HEX color.");
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
19
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
20
|
+
b: parseInt(hex.slice(4, 6), 16),
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
luminanace(hexColorCode) {
|
|
24
|
+
const rgb = this.getRgb(hexColorCode);
|
|
25
|
+
return rgb.r * 0.299 + rgb.g * 0.587 + rgb.b * 0.114;
|
|
26
|
+
},
|
|
27
|
+
invert(hexColorCode) {
|
|
28
|
+
return this.luminanace(hexColorCode) > 135 ? "#000" : "#fff"; // compare with `>=128`, but giving little more preference to white over black
|
|
29
|
+
},
|
|
30
|
+
opacity(hex, opacity) {
|
|
31
|
+
const rgb = this.getRgb(hex);
|
|
32
|
+
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${opacity})`;
|
|
33
|
+
},
|
|
34
|
+
brightness(hex, amt) {
|
|
35
|
+
const rgb = this.getRgb(hex);
|
|
36
|
+
rgb.r += amt;
|
|
37
|
+
rgb.g += amt;
|
|
38
|
+
rgb.b += amt;
|
|
39
|
+
if (rgb.r > 255) rgb.r = 255;
|
|
40
|
+
else if (rgb.r < 0) rgb.r = 0;
|
|
41
|
+
|
|
42
|
+
if (rgb.g > 255) rgb.g = 255;
|
|
43
|
+
else if (rgb.g < 0) rgb.g = 0;
|
|
44
|
+
|
|
45
|
+
if (rgb.b > 255) rgb.b = 255;
|
|
46
|
+
else if (rgb.b < 0) rgb.b = 0;
|
|
47
|
+
return `#${rgb.r.toString(16).padStart(2, "0")}${rgb.g.toString(16).padStart(2, "0")}${rgb.b.toString(16).padStart(2, "0")}`;
|
|
48
|
+
},
|
|
49
|
+
hasGoodContrast(hexColorCode1, hexColorCode2) {
|
|
50
|
+
const lum1 = this.luminanace(hexColorCode1);
|
|
51
|
+
const lum2 = this.luminanace(hexColorCode2);
|
|
52
|
+
return lum1 - lum2;
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export function isValidHexColor(colorCode) {
|
|
58
|
+
return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|[A-Fa-f0-9]{8}|[A-Fa-f0-9]{4})$/i.test(colorCode);
|
|
59
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/* For Delayed Event Handler Execution */
|
|
2
|
+
export function debounce(fn, delay) {
|
|
3
|
+
let timeoutID = null;
|
|
4
|
+
return (...args) => {
|
|
5
|
+
clearTimeout(timeoutID);
|
|
6
|
+
const that = this;
|
|
7
|
+
timeoutID = setTimeout(() => {
|
|
8
|
+
fn.apply(that, args);
|
|
9
|
+
}, delay);
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const invalidCharsRegEx = /[\s#:?&={}]/g; // used for generating valid html element ids by replacing the invalid chars with hyphen (-)
|
|
14
|
+
export const rapidocApiKey = "_rapidoc_api_key";
|
|
15
|
+
|
|
16
|
+
export function sleep(ms) {
|
|
17
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function copyToClipboard(data, e) {
|
|
21
|
+
const btnEl = e.target;
|
|
22
|
+
const textArea = document.createElement("textarea");
|
|
23
|
+
textArea.value = data;
|
|
24
|
+
textArea.style.position = "fixed"; // avoid scrolling to bottom
|
|
25
|
+
document.body.appendChild(textArea);
|
|
26
|
+
textArea.focus();
|
|
27
|
+
textArea.select();
|
|
28
|
+
try {
|
|
29
|
+
document.execCommand("copy");
|
|
30
|
+
btnEl.innerText = "Copied";
|
|
31
|
+
setTimeout(() => {
|
|
32
|
+
btnEl.innerText = "Copy";
|
|
33
|
+
}, 5000);
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.error("Unable to copy", err);
|
|
36
|
+
}
|
|
37
|
+
document.body.removeChild(textArea);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getBaseUrlFromUrl(url) {
|
|
41
|
+
const pathArray = url.split("/");
|
|
42
|
+
return `${pathArray[0]}//${pathArray[2]}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function wait(ms) {
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
setTimeout(resolve, ms);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getMatchedComponents(searchVal, component) {
|
|
52
|
+
return component.name.toLowerCase().includes(searchVal.toLowerCase());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getMatchedPaths(searchVal = "", path, tagName = "") {
|
|
56
|
+
if (!searchVal) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
const stringToSearch =
|
|
60
|
+
`${path.method} ${path.path} ${path.summary || ""} ${path.description || ""} ${path.operationId || ""} ${tagName}`.toLowerCase();
|
|
61
|
+
return stringToSearch.includes(searchVal.toLowerCase());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function isInternalOrHidden(item) {
|
|
65
|
+
if (!item || typeof item !== "object") return false;
|
|
66
|
+
return (
|
|
67
|
+
item.xInternal === true ||
|
|
68
|
+
item.xInternal === "true" ||
|
|
69
|
+
item.xHidden === true ||
|
|
70
|
+
item.xHidden === "true" ||
|
|
71
|
+
item["x-internal"] === true ||
|
|
72
|
+
item["x-internal"] === "true" ||
|
|
73
|
+
item["x-hidden"] === true ||
|
|
74
|
+
item["x-hidden"] === "true" ||
|
|
75
|
+
item.rawOperation?.["x-internal"] === true ||
|
|
76
|
+
item.rawOperation?.["x-internal"] === "true" ||
|
|
77
|
+
item.rawOperation?.["x-hidden"] === true ||
|
|
78
|
+
item.rawOperation?.["x-hidden"] === "true"
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function schemaKeys(schemaProps, result = new Set()) {
|
|
83
|
+
if (!schemaProps) {
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
Object.keys(schemaProps).forEach((key) => {
|
|
87
|
+
result.add(key);
|
|
88
|
+
if (schemaProps[key].properties) {
|
|
89
|
+
schemaKeys(schemaProps[key].properties, result);
|
|
90
|
+
} else if (schemaProps[key].items?.properties) {
|
|
91
|
+
schemaKeys(schemaProps[key].items?.properties, result);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function advancedSearch(searchVal, allSpecTags, searchOptions = []) {
|
|
98
|
+
if (!searchVal.trim() || searchOptions.length === 0) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const pathsMatched = [];
|
|
103
|
+
allSpecTags.forEach((tag) => {
|
|
104
|
+
tag.paths.forEach((path) => {
|
|
105
|
+
let stringToSearch = "";
|
|
106
|
+
if (searchOptions.includes("search-api-path")) {
|
|
107
|
+
stringToSearch = path.path;
|
|
108
|
+
}
|
|
109
|
+
if (searchOptions.includes("search-api-descr")) {
|
|
110
|
+
stringToSearch = `${stringToSearch} ${(path.summary || "") + (path.description || "")}`;
|
|
111
|
+
}
|
|
112
|
+
if (searchOptions.includes("search-api-params")) {
|
|
113
|
+
stringToSearch = `${stringToSearch} ${path.parameters?.map((v) => v.name).join(" ") || ""}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (searchOptions.includes("search-api-request-body") && path.requestBody) {
|
|
117
|
+
let schemaKeySet = new Set();
|
|
118
|
+
for (const contentType in path.requestBody?.content) {
|
|
119
|
+
if (path.requestBody.content[contentType].schema?.properties) {
|
|
120
|
+
schemaKeySet = schemaKeys(path.requestBody.content[contentType].schema?.properties);
|
|
121
|
+
}
|
|
122
|
+
stringToSearch = `${stringToSearch} ${[...schemaKeySet].join(" ")}`;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (searchOptions.includes("search-api-resp-descr")) {
|
|
127
|
+
stringToSearch = `${stringToSearch} ${Object.values(path.responses)
|
|
128
|
+
.map((v) => v.description || "")
|
|
129
|
+
.join(" ")}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (stringToSearch.toLowerCase().includes(searchVal.trim().toLowerCase())) {
|
|
133
|
+
pathsMatched.push({
|
|
134
|
+
elementId: path.elementId,
|
|
135
|
+
method: path.method,
|
|
136
|
+
path: path.path,
|
|
137
|
+
summary: path.summary || path.description || "",
|
|
138
|
+
deprecated: path.deprecated,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
return pathsMatched;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/*
|
|
147
|
+
export function prettyXml(sourceXmlString) {
|
|
148
|
+
const xmlDoc = new DOMParser().parseFromString(sourceXmlString, 'text/xml');
|
|
149
|
+
const xsltDoc = new DOMParser().parseFromString([
|
|
150
|
+
// describes how we want to modify the XML - indent everything
|
|
151
|
+
`<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
|
152
|
+
<xsl:strip-space elements="*"/>
|
|
153
|
+
<xsl:template match="para[content-style][not(text())]">
|
|
154
|
+
<xsl:value-of select="normalize-space(.)"/>
|
|
155
|
+
</xsl:template>
|
|
156
|
+
<xsl:template match="node()|@*">
|
|
157
|
+
<xsl:copy><xsl:apply-templates select="node()|@*"/></xsl:copy>
|
|
158
|
+
</xsl:template>
|
|
159
|
+
<xsl:output indent="yes"/>
|
|
160
|
+
</xsl:stylesheet>`,
|
|
161
|
+
].join('\n'), 'application/xml');
|
|
162
|
+
const xsltProcessor = new XSLTProcessor();
|
|
163
|
+
xsltProcessor.importStylesheet(xsltDoc);
|
|
164
|
+
const resultDoc = xsltProcessor.transformToDocument(xmlDoc);
|
|
165
|
+
return new XMLSerializer().serializeToString(resultDoc);
|
|
166
|
+
}
|
|
167
|
+
*/
|
|
168
|
+
|
|
169
|
+
export function downloadResource(url, fileName) {
|
|
170
|
+
if (url) {
|
|
171
|
+
const a = document.createElement("a");
|
|
172
|
+
document.body.appendChild(a);
|
|
173
|
+
a.style = "display: none";
|
|
174
|
+
a.href = url;
|
|
175
|
+
a.download = fileName;
|
|
176
|
+
a.click();
|
|
177
|
+
a.remove();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function viewResource(url) {
|
|
182
|
+
if (url) {
|
|
183
|
+
const a = document.createElement("a");
|
|
184
|
+
document.body.appendChild(a);
|
|
185
|
+
a.style = "display: none";
|
|
186
|
+
a.href = url;
|
|
187
|
+
a.target = "_blank";
|
|
188
|
+
a.click();
|
|
189
|
+
a.remove();
|
|
190
|
+
}
|
|
191
|
+
}
|