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.
Files changed (96) hide show
  1. package/README.md +171 -54
  2. package/dist/rapidoc-flow-min.js +580 -0
  3. package/dist/rapidoc-flow-min.js.LICENSE.txt +171 -0
  4. package/dist/rapidoc-min.js +645 -202
  5. package/dist/rapidoc-min.js.LICENSE.txt +381 -0
  6. package/package.json +57 -42
  7. package/src/README.md +150 -0
  8. package/src/components/README.md +91 -0
  9. package/src/components/api-request.ts +912 -0
  10. package/src/components/api-response.ts +514 -0
  11. package/src/components/json-tree.ts +171 -0
  12. package/src/components/schema-base-element.ts +329 -0
  13. package/src/components/schema-table.ts +475 -0
  14. package/src/components/schema-tree.ts +393 -0
  15. package/src/components/tag-input.ts +180 -0
  16. package/src/flow.js +3 -0
  17. package/src/index.js +6 -0
  18. package/src/oauth-receiver.ts +29 -0
  19. package/src/rapi-flow.ts +1178 -0
  20. package/src/rapidoc-mini.ts +603 -0
  21. package/src/rapidoc.ts +1854 -0
  22. package/src/styles/README.md +96 -0
  23. package/src/styles/auth-styles.js +318 -0
  24. package/src/styles/border-styles.js +24 -0
  25. package/src/styles/css-bundle.js +129 -0
  26. package/src/styles/custom-styles.js +8 -0
  27. package/src/styles/dialog-box-styles.js +102 -0
  28. package/src/styles/endpoint-styles.js +564 -0
  29. package/src/styles/flex-styles.js +16 -0
  30. package/src/styles/flow-styles.js +248 -0
  31. package/src/styles/font-styles.js +432 -0
  32. package/src/styles/info-styles.js +36 -0
  33. package/src/styles/input-styles.js +555 -0
  34. package/src/styles/layout-styles.js +396 -0
  35. package/src/styles/microlighter-styles.js +99 -0
  36. package/src/styles/nav-styles.js +633 -0
  37. package/src/styles/param-list-styles.js +261 -0
  38. package/src/styles/schema-styles.js +296 -0
  39. package/src/styles/tab-styles.js +106 -0
  40. package/src/styles/theme-tokens.js +582 -0
  41. package/src/templates/README.md +89 -0
  42. package/src/templates/advanced-search-dialog-template.js +94 -0
  43. package/src/templates/api-response-template.js +223 -0
  44. package/src/templates/callback-template.js +86 -0
  45. package/src/templates/code-samples-template.js +52 -0
  46. package/src/templates/components-template.js +86 -0
  47. package/src/templates/downloads-template.js +70 -0
  48. package/src/templates/endpoint-template.js +377 -0
  49. package/src/templates/expanded-endpoint-template.js +270 -0
  50. package/src/templates/focused-endpoint-template.js +146 -0
  51. package/src/templates/header-template.js +92 -0
  52. package/src/templates/logo-template.js +21 -0
  53. package/src/templates/main-body-template.js +102 -0
  54. package/src/templates/navbar-template.js +498 -0
  55. package/src/templates/overview-template.js +104 -0
  56. package/src/templates/request-body-template.js +673 -0
  57. package/src/templates/request-params-template.js +485 -0
  58. package/src/templates/security-scheme-template.js +1272 -0
  59. package/src/templates/server-template.js +153 -0
  60. package/src/utils/README.md +146 -0
  61. package/src/utils/arazzo/README.md +119 -0
  62. package/src/utils/arazzo/components.js +15 -0
  63. package/src/utils/arazzo/graph.js +269 -0
  64. package/src/utils/arazzo/index.js +52 -0
  65. package/src/utils/arazzo/operations.js +106 -0
  66. package/src/utils/arazzo/parse.js +196 -0
  67. package/src/utils/arazzo/refs.js +40 -0
  68. package/src/utils/arazzo/svg.js +707 -0
  69. package/src/utils/auto-color-scheme.js +99 -0
  70. package/src/utils/bind-navbar.js +175 -0
  71. package/src/utils/color-utils.js +59 -0
  72. package/src/utils/common-utils.js +191 -0
  73. package/src/utils/curl-utils.js +126 -0
  74. package/src/utils/highlighter.js +419 -0
  75. package/src/utils/markdown-utils.js +110 -0
  76. package/src/utils/mcp/README.md +155 -0
  77. package/src/utils/mcp/common.js +166 -0
  78. package/src/utils/mcp/manifest.js +382 -0
  79. package/src/utils/mcp/project/server.ts +53 -0
  80. package/src/utils/mcp/project.js +88 -0
  81. package/src/utils/mcp/runtime.js +289 -0
  82. package/src/utils/mcp/schema.js +98 -0
  83. package/src/utils/mcp/workflow-runtime.js +374 -0
  84. package/src/utils/mcp/workflows.js +240 -0
  85. package/src/utils/mcp/zip.js +102 -0
  86. package/src/utils/mcp-generator.js +47 -0
  87. package/src/utils/mock-interceptor.js +557 -0
  88. package/src/utils/oauth-utils.js +87 -0
  89. package/src/utils/request-utils.js +264 -0
  90. package/src/utils/response-utils.js +203 -0
  91. package/src/utils/sanitize.js +57 -0
  92. package/src/utils/schema-utils.js +1954 -0
  93. package/src/utils/slugger.js +187 -0
  94. package/src/utils/spec-parser.js +941 -0
  95. package/src/utils/url-utils.js +246 -0
  96. 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
+ }