rapidoc 11.0.1 → 12.1.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 +171 -54
- package/dist/rapidoc-flow-min.js +580 -0
- package/dist/rapidoc-flow-min.js.LICENSE.txt +171 -0
- package/dist/rapidoc-min.js +645 -202
- package/dist/rapidoc-min.js.LICENSE.txt +381 -0
- package/package.json +57 -42
- package/src/README.md +150 -0
- package/src/components/README.md +91 -0
- package/src/components/api-request.ts +912 -0
- package/src/components/api-response.ts +514 -0
- package/src/components/json-tree.ts +171 -0
- package/src/components/schema-base-element.ts +329 -0
- package/src/components/schema-table.ts +475 -0
- package/src/components/schema-tree.ts +393 -0
- package/src/components/tag-input.ts +180 -0
- package/src/flow.js +3 -0
- package/src/index.js +6 -0
- package/src/oauth-receiver.ts +29 -0
- package/src/rapi-flow.ts +1178 -0
- package/src/rapidoc-mini.ts +603 -0
- package/src/rapidoc.ts +1854 -0
- package/src/styles/README.md +96 -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 +564 -0
- package/src/styles/flex-styles.js +16 -0
- package/src/styles/flow-styles.js +248 -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 +633 -0
- package/src/styles/param-list-styles.js +261 -0
- package/src/styles/schema-styles.js +296 -0
- package/src/styles/tab-styles.js +106 -0
- package/src/styles/theme-tokens.js +582 -0
- package/src/templates/README.md +89 -0
- package/src/templates/advanced-search-dialog-template.js +94 -0
- package/src/templates/api-response-template.js +223 -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 +70 -0
- package/src/templates/endpoint-template.js +377 -0
- package/src/templates/expanded-endpoint-template.js +270 -0
- package/src/templates/focused-endpoint-template.js +146 -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 +498 -0
- package/src/templates/overview-template.js +104 -0
- package/src/templates/request-body-template.js +673 -0
- package/src/templates/request-params-template.js +485 -0
- package/src/templates/security-scheme-template.js +1272 -0
- package/src/templates/server-template.js +153 -0
- package/src/utils/README.md +146 -0
- package/src/utils/arazzo/README.md +119 -0
- package/src/utils/arazzo/components.js +15 -0
- package/src/utils/arazzo/graph.js +269 -0
- package/src/utils/arazzo/index.js +52 -0
- package/src/utils/arazzo/operations.js +106 -0
- package/src/utils/arazzo/parse.js +196 -0
- package/src/utils/arazzo/refs.js +40 -0
- package/src/utils/arazzo/svg.js +707 -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 +126 -0
- package/src/utils/highlighter.js +419 -0
- package/src/utils/markdown-utils.js +110 -0
- package/src/utils/mcp/README.md +155 -0
- package/src/utils/mcp/common.js +166 -0
- package/src/utils/mcp/manifest.js +382 -0
- package/src/utils/mcp/project/server.ts +53 -0
- package/src/utils/mcp/project.js +88 -0
- package/src/utils/mcp/runtime.js +289 -0
- package/src/utils/mcp/schema.js +98 -0
- package/src/utils/mcp/workflow-runtime.js +374 -0
- package/src/utils/mcp/workflows.js +240 -0
- package/src/utils/mcp/zip.js +102 -0
- package/src/utils/mcp-generator.js +47 -0
- package/src/utils/mock-interceptor.js +557 -0
- package/src/utils/oauth-utils.js +87 -0
- package/src/utils/request-utils.js +264 -0
- package/src/utils/response-utils.js +203 -0
- package/src/utils/sanitize.js +57 -0
- package/src/utils/schema-utils.js +1954 -0
- package/src/utils/slugger.js +187 -0
- package/src/utils/spec-parser.js +941 -0
- package/src/utils/url-utils.js +246 -0
- package/src/utils/xml-utils.js +61 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes header values by deduplicating comma-separated tokens (e.g. Accept headers).
|
|
3
|
+
*
|
|
4
|
+
* @param {string} value - Raw header value
|
|
5
|
+
* @returns {string} Deduplicated header value
|
|
6
|
+
*/
|
|
7
|
+
export function deduplicateHeaderValues(value) {
|
|
8
|
+
if (!value || typeof value !== "string") {
|
|
9
|
+
return value || "";
|
|
10
|
+
}
|
|
11
|
+
const seenValues = [];
|
|
12
|
+
return value
|
|
13
|
+
.split(",")
|
|
14
|
+
.map((val) => {
|
|
15
|
+
const trimmed = val.trim();
|
|
16
|
+
const normalizedValue = trimmed.toLowerCase();
|
|
17
|
+
if (seenValues.includes(normalizedValue)) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
seenValues.push(normalizedValue);
|
|
21
|
+
return trimmed;
|
|
22
|
+
})
|
|
23
|
+
.filter((val) => val !== null)
|
|
24
|
+
.join(", ");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generates a clean, copyable cURL command string from request parameters.
|
|
29
|
+
* Pure utility decoupled from the DOM.
|
|
30
|
+
*
|
|
31
|
+
* @param {object} options
|
|
32
|
+
* @param {string} [options.method='GET'] - HTTP method (GET, POST, etc.)
|
|
33
|
+
* @param {string} options.url - Request URL
|
|
34
|
+
* @param {Headers|Map|Array<[string, string]>|object} [options.headers] - Request headers
|
|
35
|
+
* @param {URLSearchParams|FormData|File|string|object} [options.body] - Request body payload
|
|
36
|
+
* @param {string} [options.contentType=''] - Content type of the payload
|
|
37
|
+
* @param {Object<string, string>} [options.partContentTypes] - Content-Type of multipart parts, by field name
|
|
38
|
+
* @param {string} [options.baseLocation] - Optional base location to resolve relative URLs
|
|
39
|
+
* @returns {string} Formatted cURL snippet
|
|
40
|
+
*/
|
|
41
|
+
export function generateCurlCommand({
|
|
42
|
+
method = "GET",
|
|
43
|
+
url = "",
|
|
44
|
+
headers,
|
|
45
|
+
body,
|
|
46
|
+
contentType = "",
|
|
47
|
+
partContentTypes = {},
|
|
48
|
+
baseLocation = typeof location !== "undefined" ? location.href : "",
|
|
49
|
+
} = {}) {
|
|
50
|
+
let curlUrl = url;
|
|
51
|
+
if (!url.startsWith("http://") && !url.startsWith("https://") && baseLocation) {
|
|
52
|
+
try {
|
|
53
|
+
curlUrl = new URL(url, baseLocation).href;
|
|
54
|
+
} catch {
|
|
55
|
+
curlUrl = url;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let curl = `curl -X ${method.toUpperCase()} "${curlUrl}" \\\n`;
|
|
60
|
+
|
|
61
|
+
// Process headers into key-value pairs with deduplicated comma values
|
|
62
|
+
const headerEntries = [];
|
|
63
|
+
if (headers instanceof Headers || (headers && typeof headers.forEach === "function")) {
|
|
64
|
+
headers.forEach((value, key) => {
|
|
65
|
+
headerEntries.push([key, deduplicateHeaderValues(value)]);
|
|
66
|
+
});
|
|
67
|
+
} else if (Array.isArray(headers)) {
|
|
68
|
+
headers.forEach(([key, value]) => {
|
|
69
|
+
headerEntries.push([key, deduplicateHeaderValues(value)]);
|
|
70
|
+
});
|
|
71
|
+
} else if (headers && typeof headers === "object") {
|
|
72
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
73
|
+
headerEntries.push([key, deduplicateHeaderValues(String(value))]);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let curlHeaders = headerEntries.map(([key, value]) => ` -H '${key}: ${value}'`).join("\\\n");
|
|
78
|
+
if (curlHeaders) {
|
|
79
|
+
curlHeaders = `${curlHeaders} \\\n`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let curlData = "";
|
|
83
|
+
let curlForm = "";
|
|
84
|
+
|
|
85
|
+
if (typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams) {
|
|
86
|
+
curlData = ` -d ${body.toString()} \\\n`;
|
|
87
|
+
} else if (typeof File !== "undefined" && body instanceof File) {
|
|
88
|
+
curlData = ` --data-binary @${body.name} \\\n`;
|
|
89
|
+
} else if (typeof FormData !== "undefined" && body instanceof FormData) {
|
|
90
|
+
curlForm = Array.from(body)
|
|
91
|
+
.map(([key, value]) => {
|
|
92
|
+
const type = partContentTypes?.[key] ? `;type=${partContentTypes[key]}` : "";
|
|
93
|
+
if (typeof File !== "undefined" && value instanceof File) {
|
|
94
|
+
return ` -F "${key}=@${value.name}${value.type ? "" : type}"`;
|
|
95
|
+
}
|
|
96
|
+
const sanitizedVal =
|
|
97
|
+
typeof value === "string" ? value.replace(/\\/g, "\\\\").replace(/"/g, '\\"') : value;
|
|
98
|
+
return ` -F "${key}=${sanitizedVal}${type}"`;
|
|
99
|
+
})
|
|
100
|
+
.join("\\\n");
|
|
101
|
+
if (curlForm) {
|
|
102
|
+
curlForm = `${curlForm} \\\n`;
|
|
103
|
+
}
|
|
104
|
+
} else if (body !== undefined && body !== null && body !== "") {
|
|
105
|
+
const rawBody = typeof body === "object" ? JSON.stringify(body) : String(body);
|
|
106
|
+
const isJson =
|
|
107
|
+
contentType.includes("json") ||
|
|
108
|
+
(rawBody.trim().startsWith("{") && rawBody.trim().endsWith("}"));
|
|
109
|
+
|
|
110
|
+
if (isJson) {
|
|
111
|
+
try {
|
|
112
|
+
const compactJson = JSON.stringify(JSON.parse(rawBody));
|
|
113
|
+
curlData = ` -d '${compactJson.replace(/'/g, "'\\''")}' \\\n`;
|
|
114
|
+
} catch {
|
|
115
|
+
// If JSON.parse fails, fall through to regular string escape
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!curlData) {
|
|
120
|
+
curlData = ` -d '${rawBody.replace(/'/g, "'\"'\"'")}' \\\n`;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const result = `${curl}${curlHeaders}${curlData}${curlForm}`;
|
|
125
|
+
return result.endsWith(" \\\n") ? result.slice(0, -3) : result;
|
|
126
|
+
}
|