rapidoc 11.0.1 → 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 +47 -35
- 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
package/src/rapidoc.ts
ADDED
|
@@ -0,0 +1,1808 @@
|
|
|
1
|
+
import { LitElement, noChange } from "lit";
|
|
2
|
+
import { customElement, property } from "lit/decorators.js";
|
|
3
|
+
import { fixRenderedAnchorLinks } from "~/utils/markdown-utils";
|
|
4
|
+
import { marked } from "marked";
|
|
5
|
+
import Slugger from "github-slugger";
|
|
6
|
+
import { scheduleHighlight } from "~/utils/highlighter";
|
|
7
|
+
|
|
8
|
+
// Styles
|
|
9
|
+
import FontStyles from "~/styles/font-styles";
|
|
10
|
+
import InputStyles from "~/styles/input-styles";
|
|
11
|
+
import FlexStyles from "~/styles/flex-styles";
|
|
12
|
+
import AuthStyles from "~/styles/auth-styles";
|
|
13
|
+
import ParamListStyles from "~/styles/param-list-styles";
|
|
14
|
+
import EndpointStyles from "~/styles/endpoint-styles";
|
|
15
|
+
import MicrolighterStyles from "~/styles/microlighter-styles";
|
|
16
|
+
import TabStyles from "~/styles/tab-styles";
|
|
17
|
+
import NavStyles from "~/styles/nav-styles";
|
|
18
|
+
import InfoStyles from "~/styles/info-styles";
|
|
19
|
+
import CustomStyles from "~/styles/custom-styles";
|
|
20
|
+
import DialogBoxStyles from "~/styles/dialog-box-styles";
|
|
21
|
+
import LayoutStyles from "~/styles/layout-styles";
|
|
22
|
+
import ThemeTokens, {
|
|
23
|
+
DEFAULT_THEME,
|
|
24
|
+
applyCustomTheme,
|
|
25
|
+
isValidHexColor,
|
|
26
|
+
normalizeTheme,
|
|
27
|
+
} from "~/styles/theme-tokens";
|
|
28
|
+
|
|
29
|
+
import {
|
|
30
|
+
advancedSearch,
|
|
31
|
+
getMatchedPaths,
|
|
32
|
+
getMatchedComponents,
|
|
33
|
+
rapidocApiKey,
|
|
34
|
+
sleep,
|
|
35
|
+
} from "~/utils/common-utils";
|
|
36
|
+
import ProcessSpec from "~/utils/spec-parser";
|
|
37
|
+
import { enableMockServer, disableMockServer, updateMockConfig } from "~/utils/mock-interceptor";
|
|
38
|
+
import mainBodyTemplate from "~/templates/main-body-template";
|
|
39
|
+
import { expandParentNavContainers } from "~/templates/navbar-template";
|
|
40
|
+
import { applyApiKey, onClearAllApiKeys } from "~/templates/security-scheme-template";
|
|
41
|
+
import { setApiServer } from "~/templates/server-template";
|
|
42
|
+
import {
|
|
43
|
+
generateMcpServer as mcpServerGenerator,
|
|
44
|
+
extractMcpOperations,
|
|
45
|
+
estimateTokenCost,
|
|
46
|
+
parseMcpTargets,
|
|
47
|
+
} from "~/utils/mcp-generator";
|
|
48
|
+
import { bindNavBar } from "~/utils/bind-navbar";
|
|
49
|
+
import { methodPathSlug } from "~/utils/slugger";
|
|
50
|
+
import { AutoColorSchemeController } from "~/utils/auto-color-scheme";
|
|
51
|
+
|
|
52
|
+
const paramsConverter = {
|
|
53
|
+
fromAttribute: (attr) => {
|
|
54
|
+
if (!attr) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const trimmed = attr.trim();
|
|
58
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(trimmed);
|
|
61
|
+
} catch {
|
|
62
|
+
return trimmed;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return trimmed;
|
|
66
|
+
},
|
|
67
|
+
toAttribute: (prop) => {
|
|
68
|
+
if (typeof prop === "object" && prop !== null) {
|
|
69
|
+
return JSON.stringify(prop);
|
|
70
|
+
}
|
|
71
|
+
return prop;
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Input that marks the reader's first interaction with the page (see getContentScrollBehavior)
|
|
76
|
+
const INTERACTION_EVENTS = ["pointerdown", "keydown", "wheel", "touchstart"];
|
|
77
|
+
|
|
78
|
+
@customElement("rapi-doc")
|
|
79
|
+
export default class RapiDoc extends LitElement {
|
|
80
|
+
constructor() {
|
|
81
|
+
super();
|
|
82
|
+
this.showSummaryWhenCollapsed = true;
|
|
83
|
+
this._intersectingElements = new Map();
|
|
84
|
+
// Will activate intersection observer only after spec load and hash analyze
|
|
85
|
+
// to scroll to the proper element without being reverted by observer behavior
|
|
86
|
+
this.isIntersectionObserverActive = false;
|
|
87
|
+
this._userSetLayout = false;
|
|
88
|
+
this._isDefaultingLayout = false;
|
|
89
|
+
this._observerLockTimeout = null;
|
|
90
|
+
this._observerLockCleanup = null;
|
|
91
|
+
// Set on the reader's first pointer, key, wheel or touch input (see getContentScrollBehavior)
|
|
92
|
+
this._readerHasInteracted = false;
|
|
93
|
+
this._onFirstInteraction = null;
|
|
94
|
+
this.initIntersectionObserver();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Heading
|
|
98
|
+
@property({ type: String, attribute: "heading-text" }) headingText = "";
|
|
99
|
+
@property({ type: String, attribute: "goto-path" }) gotoPath = "";
|
|
100
|
+
|
|
101
|
+
// Spec
|
|
102
|
+
@property({ type: String, attribute: "update-route" }) updateRoute = "true";
|
|
103
|
+
@property({ type: String, attribute: "spec-url" }) specUrl = "";
|
|
104
|
+
@property({ type: String, attribute: "sort-tags" }) sortTags = "false";
|
|
105
|
+
@property({ type: String, attribute: "sort-schemas" }) sortSchemas = "false";
|
|
106
|
+
@property({ type: String, attribute: "generate-missing-tags" }) generateMissingTags = "false";
|
|
107
|
+
@property({ type: String, attribute: "sort-endpoints-by" }) sortEndpointsBy = "path";
|
|
108
|
+
@property({ type: String, attribute: false }) specFile: any;
|
|
109
|
+
|
|
110
|
+
// UI Layouts
|
|
111
|
+
@property({ type: String, reflect: true }) layout = "row";
|
|
112
|
+
@property({ type: String, attribute: "render-style" }) renderStyle = "read";
|
|
113
|
+
@property({ type: String, attribute: "default-schema-tab" }) defaultSchemaTab = "example";
|
|
114
|
+
@property({ type: String, attribute: "response-area-height" }) responseAreaHeight = "400px";
|
|
115
|
+
@property({ type: String, attribute: "fill-request-fields-with-example" })
|
|
116
|
+
fillRequestFieldsWithExample = "true";
|
|
117
|
+
@property({ type: String, attribute: "persist-auth" }) persistAuth = "false";
|
|
118
|
+
|
|
119
|
+
// Schema Styles
|
|
120
|
+
@property({ type: String, attribute: "schema-style" }) schemaStyle = "tree";
|
|
121
|
+
@property({ type: Number, attribute: "schema-expand-level" }) schemaExpandLevel = 99999;
|
|
122
|
+
@property({ type: String, attribute: "schema-description-expanded" }) schemaDescriptionExpanded =
|
|
123
|
+
"false";
|
|
124
|
+
@property({ type: String, attribute: "schema-hide-read-only" }) schemaHideReadOnly = "true";
|
|
125
|
+
@property({ type: String, attribute: "schema-hide-write-only" }) schemaHideWriteOnly = "true";
|
|
126
|
+
|
|
127
|
+
// API Server
|
|
128
|
+
@property({ type: String, attribute: "api-key-name" }) apiKeyName = "";
|
|
129
|
+
@property({ type: String, attribute: "api-key-location" }) apiKeyLocation = "header";
|
|
130
|
+
@property({ type: String, attribute: "api-key-value" }) apiKeyValue = "-";
|
|
131
|
+
@property({ type: String, attribute: "default-api-server" }) defaultApiServerUrl = "";
|
|
132
|
+
@property({ type: String, attribute: "server-url" }) serverUrl = "";
|
|
133
|
+
@property({ type: String, attribute: "oauth-receiver" }) oauthReceiver = "oauth-receiver.html";
|
|
134
|
+
@property({ type: Object, attribute: "additional-authorize-params", converter: paramsConverter })
|
|
135
|
+
additionalAuthorizeParams: any;
|
|
136
|
+
@property({ type: Object, attribute: "additional-token-params", converter: paramsConverter })
|
|
137
|
+
additionalTokenParams: any;
|
|
138
|
+
|
|
139
|
+
// Mock Server
|
|
140
|
+
@property({ type: String, attribute: "mock-server" }) mockServer = "false";
|
|
141
|
+
@property({ type: String, attribute: "mock-server-status-code" }) mockServerStatusCode = "200";
|
|
142
|
+
@property({ type: String, attribute: "mock-server-status-strategy" }) mockServerStatusStrategy =
|
|
143
|
+
"example";
|
|
144
|
+
@property({ type: Number, attribute: "mock-server-delay" }) mockServerDelay = 0;
|
|
145
|
+
@property({ type: String, attribute: "mock-server-log" }) mockServerLog = "false";
|
|
146
|
+
|
|
147
|
+
// Hide/Show Sections & Enable Disable actions
|
|
148
|
+
@property({ type: String, attribute: "show-header" }) showHeader = "true";
|
|
149
|
+
@property({ type: String, attribute: "show-side-nav" }) showSideNav = "true";
|
|
150
|
+
@property({ type: String, attribute: "show-info" }) showInfo = "true";
|
|
151
|
+
@property({ type: String, attribute: "allow-authentication" }) allowAuthentication = "true";
|
|
152
|
+
@property({ type: String, attribute: "allow-try" }) allowTry = "true";
|
|
153
|
+
@property({ type: String, attribute: "show-curl-before-try" }) showCurlBeforeTry = "false";
|
|
154
|
+
@property({ type: String, attribute: "allow-spec-url-load" }) allowSpecUrlLoad = "true";
|
|
155
|
+
@property({ type: String, attribute: "allow-spec-file-load" }) allowSpecFileLoad = "true";
|
|
156
|
+
@property({ type: String, attribute: "allow-spec-file-download" }) allowSpecFileDownload =
|
|
157
|
+
"false";
|
|
158
|
+
@property({ type: String, attribute: "allow-search" }) allowSearch = "true";
|
|
159
|
+
@property({ type: String, attribute: "allow-advanced-search" }) allowAdvancedSearch = "true";
|
|
160
|
+
@property({ type: String, attribute: "allow-server-selection" }) allowServerSelection = "true";
|
|
161
|
+
@property({ type: String, attribute: "allow-schema-description-expand-toggle" })
|
|
162
|
+
allowSchemaDescriptionExpandToggle = "true";
|
|
163
|
+
@property({ type: String, attribute: "mcp" }) mcp = "true";
|
|
164
|
+
@property({ type: String, attribute: "mcp-server-name" }) mcpServerName = "";
|
|
165
|
+
@property({ type: String, attribute: "show-components" }) showComponents = "false";
|
|
166
|
+
@property({ type: String, attribute: "page-direction" }) pageDirection = "ltr";
|
|
167
|
+
@property({ type: String, attribute: "scroll-behavior" }) scrollBehavior = "smooth";
|
|
168
|
+
@property({ type: String, attribute: "auto-height" }) autoHeight = "false";
|
|
169
|
+
@property({ type: String, attribute: "scroll-container" }) scrollContainer = "";
|
|
170
|
+
@property({ type: String, attribute: "scroll-offset" }) scrollOffset = "";
|
|
171
|
+
|
|
172
|
+
// Main Colors and Font
|
|
173
|
+
@property({ type: String, reflect: true }) theme = DEFAULT_THEME;
|
|
174
|
+
@property({ type: String, attribute: "primary-color" }) primaryColor = "";
|
|
175
|
+
@property({ type: String, attribute: "color-scheme", reflect: true }) colorScheme = "dark";
|
|
176
|
+
// color-scheme="auto": follows the page's theme (see utils/auto-color-scheme.js)
|
|
177
|
+
_autoColorScheme = new AutoColorSchemeController(this);
|
|
178
|
+
@property({ type: String, reflect: true }) radius = "sm";
|
|
179
|
+
@property({ type: String, reflect: true }) scale = "default";
|
|
180
|
+
@property({ type: String, attribute: "css-file" }) cssFile = "";
|
|
181
|
+
@property({ type: String, attribute: "css-classes" }) cssClasses = "";
|
|
182
|
+
|
|
183
|
+
// Nav Bar
|
|
184
|
+
@property({ type: String, attribute: "nav-item-spacing", reflect: true }) navItemSpacing =
|
|
185
|
+
"default";
|
|
186
|
+
@property({ type: String, attribute: "nav-item-overflow", reflect: true }) navItemOverflow =
|
|
187
|
+
"ellipsis";
|
|
188
|
+
@property({ type: String, attribute: "show-method-in-nav-bar" }) showMethodInNavBar = "false";
|
|
189
|
+
@property({ type: String, attribute: "use-path-in-nav-bar" }) usePathInNavBar = "false";
|
|
190
|
+
@property({ type: String, attribute: "info-description-headings-in-navbar" })
|
|
191
|
+
infoDescriptionHeadingsInNavBar = "false";
|
|
192
|
+
|
|
193
|
+
// Fetch Options
|
|
194
|
+
@property({ type: String, attribute: "fetch-credentials" }) fetchCredentials = "";
|
|
195
|
+
|
|
196
|
+
// Filters
|
|
197
|
+
@property({ type: String, attribute: "match-paths" }) matchPaths = "";
|
|
198
|
+
@property({ type: String, attribute: "match-type" }) matchType = "includes";
|
|
199
|
+
@property({ type: String, attribute: "remove-endpoints-with-badge-label-as" })
|
|
200
|
+
removeEndpointsWithBadgeLabelAs = "";
|
|
201
|
+
|
|
202
|
+
// Internal Properties
|
|
203
|
+
@property({ type: Boolean }) loading = false;
|
|
204
|
+
@property({ type: String }) focusedElementId = "";
|
|
205
|
+
@property({ type: Object }) advancedSearchMatches: any;
|
|
206
|
+
@property({ type: String }) searchVal = "";
|
|
207
|
+
|
|
208
|
+
static get styles() {
|
|
209
|
+
return [
|
|
210
|
+
ThemeTokens,
|
|
211
|
+
FontStyles,
|
|
212
|
+
InputStyles,
|
|
213
|
+
FlexStyles,
|
|
214
|
+
AuthStyles,
|
|
215
|
+
ParamListStyles,
|
|
216
|
+
EndpointStyles,
|
|
217
|
+
MicrolighterStyles,
|
|
218
|
+
TabStyles,
|
|
219
|
+
NavStyles,
|
|
220
|
+
InfoStyles,
|
|
221
|
+
DialogBoxStyles,
|
|
222
|
+
LayoutStyles,
|
|
223
|
+
CustomStyles,
|
|
224
|
+
];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Startup
|
|
228
|
+
connectedCallback() {
|
|
229
|
+
super.connectedCallback();
|
|
230
|
+
this.initIntersectionObserver();
|
|
231
|
+
if (this.shadowRoot && this.shadowRoot.querySelector(".main-content")) {
|
|
232
|
+
this._hasDsd = true;
|
|
233
|
+
}
|
|
234
|
+
const parent = this.parentElement;
|
|
235
|
+
if (parent && parent.tagName === "BODY") {
|
|
236
|
+
if (parent.offsetWidth === 0 && parent.style.width === "") {
|
|
237
|
+
parent.style.width = "100vw";
|
|
238
|
+
}
|
|
239
|
+
if (parent.offsetHeight === 0 && parent.style.height === "") {
|
|
240
|
+
parent.style.height = "100vh";
|
|
241
|
+
}
|
|
242
|
+
if (!parent.style.marginTop) {
|
|
243
|
+
parent.style.marginTop = "0";
|
|
244
|
+
}
|
|
245
|
+
if (!parent.style.marginRight) {
|
|
246
|
+
parent.style.marginRight = "0";
|
|
247
|
+
}
|
|
248
|
+
if (!parent.style.marginBottom) {
|
|
249
|
+
parent.style.marginBottom = "0";
|
|
250
|
+
}
|
|
251
|
+
if (!parent.style.marginLeft) {
|
|
252
|
+
parent.style.marginLeft = "0";
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (!this.renderStyle || !"read, view, focused,".includes(`${this.renderStyle},`)) {
|
|
257
|
+
this.renderStyle = "focused";
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (this.hasAttribute("layout")) {
|
|
261
|
+
const attrLayout = this.getAttribute("layout");
|
|
262
|
+
if ("row, column,".includes(`${attrLayout},`)) {
|
|
263
|
+
this.layout = attrLayout;
|
|
264
|
+
this._userSetLayout = true;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (!this._userSetLayout) {
|
|
269
|
+
const defaultLayout =
|
|
270
|
+
this.renderStyle === "read" || this.renderStyle === "focused" ? "column" : "row";
|
|
271
|
+
this._isDefaultingLayout = true;
|
|
272
|
+
this.layout = defaultLayout;
|
|
273
|
+
this.setAttribute("layout", defaultLayout);
|
|
274
|
+
this._isDefaultingLayout = false;
|
|
275
|
+
} else if (!this.layout || !"row, column,".includes(`${this.layout},`)) {
|
|
276
|
+
this.layout =
|
|
277
|
+
this.renderStyle === "read" || this.renderStyle === "focused" ? "column" : "row";
|
|
278
|
+
this.setAttribute("layout", this.layout);
|
|
279
|
+
}
|
|
280
|
+
if (!this.schemaStyle || !"tree, table,".includes(`${this.schemaStyle},`)) {
|
|
281
|
+
this.schemaStyle = "tree";
|
|
282
|
+
}
|
|
283
|
+
if (this.theme === "light" || this.theme === "dark") {
|
|
284
|
+
this.colorScheme = this.theme;
|
|
285
|
+
this.theme = DEFAULT_THEME;
|
|
286
|
+
}
|
|
287
|
+
if (this.primaryColor && isValidHexColor(this.primaryColor)) {
|
|
288
|
+
this.theme = this.primaryColor;
|
|
289
|
+
}
|
|
290
|
+
const normalizedTheme = normalizeTheme(this.theme);
|
|
291
|
+
this.theme = normalizedTheme.theme;
|
|
292
|
+
this.setAttribute("theme", this.theme);
|
|
293
|
+
if (!this.colorScheme || !"dark, light, system,".includes(`${this.colorScheme},`)) {
|
|
294
|
+
this.colorScheme = "dark";
|
|
295
|
+
}
|
|
296
|
+
if (!this.radius || !"none, sm, md, lg, full,".includes(`${this.radius},`)) {
|
|
297
|
+
this.radius = "sm";
|
|
298
|
+
}
|
|
299
|
+
if (!this.scale || !"sm, default, lg,".includes(`${this.scale},`)) {
|
|
300
|
+
this.scale = "default";
|
|
301
|
+
}
|
|
302
|
+
if (!this.navItemSpacing || !"compact, default, relaxed,".includes(`${this.navItemSpacing},`)) {
|
|
303
|
+
this.navItemSpacing = "default";
|
|
304
|
+
}
|
|
305
|
+
if (!this.getAttribute("theme")) {
|
|
306
|
+
this.setAttribute("theme", this.theme);
|
|
307
|
+
}
|
|
308
|
+
if (!this.getAttribute("color-scheme")) {
|
|
309
|
+
this.setAttribute("color-scheme", this.colorScheme);
|
|
310
|
+
}
|
|
311
|
+
if (!this.getAttribute("radius")) {
|
|
312
|
+
this.setAttribute("radius", this.radius);
|
|
313
|
+
}
|
|
314
|
+
if (!this.getAttribute("scale")) {
|
|
315
|
+
this.setAttribute("scale", this.scale);
|
|
316
|
+
}
|
|
317
|
+
applyCustomTheme(this, this.theme);
|
|
318
|
+
if (
|
|
319
|
+
!this.defaultSchemaTab ||
|
|
320
|
+
!"example, schema, model,".includes(`${this.defaultSchemaTab},`)
|
|
321
|
+
) {
|
|
322
|
+
this.defaultSchemaTab = "example";
|
|
323
|
+
} else if (this.defaultSchemaTab === "model") {
|
|
324
|
+
this.defaultSchemaTab = "schema";
|
|
325
|
+
}
|
|
326
|
+
if (!this.schemaExpandLevel || this.schemaExpandLevel < 1) {
|
|
327
|
+
this.schemaExpandLevel = 99999;
|
|
328
|
+
}
|
|
329
|
+
if (
|
|
330
|
+
!this.schemaDescriptionExpanded ||
|
|
331
|
+
!"true, false,".includes(`${this.schemaDescriptionExpanded},`)
|
|
332
|
+
) {
|
|
333
|
+
this.schemaDescriptionExpanded = "false";
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (!this.schemaHideReadOnly || !"default, never,".includes(`${this.schemaHideReadOnly},`)) {
|
|
337
|
+
this.schemaHideReadOnly = "default";
|
|
338
|
+
}
|
|
339
|
+
if (!this.schemaHideWriteOnly || !"default, never,".includes(`${this.schemaHideWriteOnly},`)) {
|
|
340
|
+
this.schemaHideWriteOnly = "default";
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (
|
|
344
|
+
!this.fillRequestFieldsWithExample ||
|
|
345
|
+
!"true, false,".includes(`${this.fillRequestFieldsWithExample},`)
|
|
346
|
+
) {
|
|
347
|
+
this.fillRequestFieldsWithExample = "true";
|
|
348
|
+
}
|
|
349
|
+
if (!this.persistAuth || !"true, false,".includes(`${this.persistAuth},`)) {
|
|
350
|
+
this.persistAuth = "false";
|
|
351
|
+
}
|
|
352
|
+
if (!this.responseAreaHeight) {
|
|
353
|
+
this.responseAreaHeight = "400px";
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (!this.allowSearch || !"true, false,".includes(`${this.allowSearch},`)) {
|
|
357
|
+
this.allowSearch = "true";
|
|
358
|
+
}
|
|
359
|
+
if (!this.allowAdvancedSearch || !"true, false,".includes(`${this.allowAdvancedSearch},`)) {
|
|
360
|
+
this.allowAdvancedSearch = "true";
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (!this.allowTry || !"true, false,".includes(`${this.allowTry},`)) {
|
|
364
|
+
this.allowTry = "true";
|
|
365
|
+
}
|
|
366
|
+
if (!this.apiKeyValue) {
|
|
367
|
+
this.apiKeyValue = "-";
|
|
368
|
+
}
|
|
369
|
+
if (!this.apiKeyLocation) {
|
|
370
|
+
this.apiKeyLocation = "header";
|
|
371
|
+
}
|
|
372
|
+
if (!this.apiKeyName) {
|
|
373
|
+
this.apiKeyName = "";
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (!this.oauthReceiver) {
|
|
377
|
+
this.oauthReceiver = "oauth-receiver.html";
|
|
378
|
+
}
|
|
379
|
+
if (!this.updateRoute || !"true, false,".includes(`${this.updateRoute},`)) {
|
|
380
|
+
this.updateRoute = "true";
|
|
381
|
+
}
|
|
382
|
+
if (!this.sortTags || !"true, false,".includes(`${this.sortTags},`)) {
|
|
383
|
+
this.sortTags = "false";
|
|
384
|
+
}
|
|
385
|
+
if (!this.sortSchemas || !"true, false,".includes(`${this.sortSchemas},`)) {
|
|
386
|
+
this.sortSchemas = "false";
|
|
387
|
+
}
|
|
388
|
+
if (!this.generateMissingTags || !"true, false,".includes(`${this.generateMissingTags},`)) {
|
|
389
|
+
this.generateMissingTags = "false";
|
|
390
|
+
}
|
|
391
|
+
if (
|
|
392
|
+
!this.sortEndpointsBy ||
|
|
393
|
+
!"method, path, summary, none,".includes(`${this.sortEndpointsBy},`)
|
|
394
|
+
) {
|
|
395
|
+
this.sortEndpointsBy = "path";
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (!this.navItemSpacing || !"compact, relaxed, default,".includes(`${this.navItemSpacing},`)) {
|
|
399
|
+
this.navItemSpacing = "default";
|
|
400
|
+
}
|
|
401
|
+
if (
|
|
402
|
+
!this.showMethodInNavBar ||
|
|
403
|
+
!"false, as-plain-text, as-colored-text, as-colored-block,".includes(
|
|
404
|
+
`${this.showMethodInNavBar},`,
|
|
405
|
+
)
|
|
406
|
+
) {
|
|
407
|
+
this.showMethodInNavBar = "false";
|
|
408
|
+
}
|
|
409
|
+
if (!this.usePathInNavBar || !"true, false,".includes(`${this.usePathInNavBar},`)) {
|
|
410
|
+
this.usePathInNavBar = "false";
|
|
411
|
+
}
|
|
412
|
+
if (!this.navItemOverflow || !"ellipsis, wrap,".includes(`${this.navItemOverflow},`)) {
|
|
413
|
+
this.navItemOverflow = "ellipsis";
|
|
414
|
+
}
|
|
415
|
+
if (!this.getAttribute("nav-item-overflow")) {
|
|
416
|
+
this.setAttribute("nav-item-overflow", this.navItemOverflow);
|
|
417
|
+
}
|
|
418
|
+
if (!this.showInfo || !"true, false,".includes(`${this.showInfo},`)) {
|
|
419
|
+
this.showInfo = "true";
|
|
420
|
+
}
|
|
421
|
+
if (!this.allowServerSelection || !"true, false,".includes(`${this.allowServerSelection},`)) {
|
|
422
|
+
this.allowServerSelection = "true";
|
|
423
|
+
}
|
|
424
|
+
if (!this.allowAuthentication || !"true, false,".includes(`${this.allowAuthentication},`)) {
|
|
425
|
+
this.allowAuthentication = "true";
|
|
426
|
+
}
|
|
427
|
+
if (
|
|
428
|
+
!this.allowSchemaDescriptionExpandToggle ||
|
|
429
|
+
!"true, false,".includes(`${this.allowSchemaDescriptionExpandToggle},`)
|
|
430
|
+
) {
|
|
431
|
+
this.allowSchemaDescriptionExpandToggle = "true";
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (!this.showSideNav || !"true false".includes(this.showSideNav)) {
|
|
435
|
+
this.showSideNav = "true";
|
|
436
|
+
}
|
|
437
|
+
if (!this.showComponents || !"true false".includes(this.showComponents)) {
|
|
438
|
+
this.showComponents = "false";
|
|
439
|
+
}
|
|
440
|
+
if (
|
|
441
|
+
!this.infoDescriptionHeadingsInNavBar ||
|
|
442
|
+
!"true, false,".includes(`${this.infoDescriptionHeadingsInNavBar},`)
|
|
443
|
+
) {
|
|
444
|
+
this.infoDescriptionHeadingsInNavBar = "false";
|
|
445
|
+
}
|
|
446
|
+
if (
|
|
447
|
+
!this.fetchCredentials ||
|
|
448
|
+
!"omit, same-origin, include,".includes(`${this.fetchCredentials},`)
|
|
449
|
+
) {
|
|
450
|
+
this.fetchCredentials = "";
|
|
451
|
+
}
|
|
452
|
+
if (!this.scrollBehavior || !"smooth, auto,".includes(`${this.scrollBehavior},`)) {
|
|
453
|
+
this.scrollBehavior = "auto";
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (!this.matchType || !"includes regex".includes(this.matchType)) {
|
|
457
|
+
this.matchType = "includes";
|
|
458
|
+
}
|
|
459
|
+
if (!this.matchPaths) {
|
|
460
|
+
this.matchPaths = "";
|
|
461
|
+
}
|
|
462
|
+
if (!this.removeEndpointsWithBadgeLabelAs) {
|
|
463
|
+
this.removeEndpointsWithBadgeLabelAs = "";
|
|
464
|
+
}
|
|
465
|
+
if (!this.cssFile) {
|
|
466
|
+
this.cssFile = null;
|
|
467
|
+
}
|
|
468
|
+
if (!this.cssClasses) {
|
|
469
|
+
this.cssClasses = "";
|
|
470
|
+
}
|
|
471
|
+
if (!this.mcp) {
|
|
472
|
+
this.mcp = "none";
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
window.addEventListener(
|
|
476
|
+
"hashchange",
|
|
477
|
+
() => {
|
|
478
|
+
this.scrollToPath(this.getElementIDFromURL());
|
|
479
|
+
},
|
|
480
|
+
true,
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
if (!this._readerHasInteracted && !this._onFirstInteraction) {
|
|
484
|
+
this._onFirstInteraction = () => {
|
|
485
|
+
this._readerHasInteracted = true;
|
|
486
|
+
this.removeFirstInteractionListeners();
|
|
487
|
+
};
|
|
488
|
+
INTERACTION_EVENTS.forEach((type) =>
|
|
489
|
+
window.addEventListener(type, this._onFirstInteraction, { capture: true, passive: true }),
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Auto-hydrate from inlined spec or spec-url attribute if not already loaded
|
|
494
|
+
if (!this.resolvedSpec) {
|
|
495
|
+
const inlinedSpecEl = this.querySelector(
|
|
496
|
+
'script[type="application/json"][slot="rapidoc-spec"]',
|
|
497
|
+
);
|
|
498
|
+
if (inlinedSpecEl?.textContent) {
|
|
499
|
+
try {
|
|
500
|
+
const parsed = JSON.parse(inlinedSpecEl.textContent);
|
|
501
|
+
this.loadSpec(parsed);
|
|
502
|
+
} catch {
|
|
503
|
+
// ignore parse error
|
|
504
|
+
}
|
|
505
|
+
} else if (this.getAttribute("spec-url")) {
|
|
506
|
+
this.loadSpec(this.getAttribute("spec-url"));
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Cleanup
|
|
512
|
+
disconnectedCallback() {
|
|
513
|
+
if (this._intersectionRaf) {
|
|
514
|
+
cancelAnimationFrame(this._intersectionRaf);
|
|
515
|
+
}
|
|
516
|
+
if (typeof this._observerLockCleanup === "function") {
|
|
517
|
+
this._observerLockCleanup();
|
|
518
|
+
this._observerLockCleanup = null;
|
|
519
|
+
}
|
|
520
|
+
if (this._observerLockTimeout) {
|
|
521
|
+
clearTimeout(this._observerLockTimeout);
|
|
522
|
+
this._observerLockTimeout = null;
|
|
523
|
+
}
|
|
524
|
+
if (this.intersectionObserver) {
|
|
525
|
+
this.intersectionObserver.disconnect();
|
|
526
|
+
}
|
|
527
|
+
this.removeFirstInteractionListeners();
|
|
528
|
+
if (this.mockServer === "true") {
|
|
529
|
+
disableMockServer();
|
|
530
|
+
}
|
|
531
|
+
super.disconnectedCallback();
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
removeFirstInteractionListeners() {
|
|
535
|
+
if (!this._onFirstInteraction) return;
|
|
536
|
+
INTERACTION_EVENTS.forEach((type) =>
|
|
537
|
+
window.removeEventListener(type, this._onFirstInteraction, { capture: true }),
|
|
538
|
+
);
|
|
539
|
+
this._onFirstInteraction = null;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Behavior of scrolls that move the content to a section. Scrolls before the reader's first input are
|
|
544
|
+
* load-time navigation (location hash, goto-path, back/forward) and jump, like the browser's own
|
|
545
|
+
* fragment navigation. A smooth scroll advances only while the page renders frames, and a background
|
|
546
|
+
* tab renders none: the scroll would not happen, and the active-section tracking would then report the
|
|
547
|
+
* section at the top of the page (and rewrite the URL to it).
|
|
548
|
+
*/
|
|
549
|
+
getContentScrollBehavior() {
|
|
550
|
+
return this._readerHasInteracted ? this.scrollBehavior : "instant";
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
infoDescriptionHeadingRenderer() {
|
|
554
|
+
const renderer = new marked.Renderer();
|
|
555
|
+
const slugger = new Slugger();
|
|
556
|
+
renderer.heading = ({ text, depth }) =>
|
|
557
|
+
`<h${depth} class="observe-me" id="${slugger.slug(text)}">${text}</h${depth}>`;
|
|
558
|
+
fixRenderedAnchorLinks(renderer);
|
|
559
|
+
return renderer;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
willUpdate(changedProperties) {
|
|
563
|
+
super.willUpdate?.(changedProperties);
|
|
564
|
+
if (this.defaultSchemaTab === "model") {
|
|
565
|
+
this.defaultSchemaTab = "schema";
|
|
566
|
+
}
|
|
567
|
+
if (changedProperties.has("layout") && !this._isDefaultingLayout) {
|
|
568
|
+
this._userSetLayout = Boolean(this.layout && "row, column,".includes(`${this.layout},`));
|
|
569
|
+
}
|
|
570
|
+
if (changedProperties.has("renderStyle") && !this._userSetLayout) {
|
|
571
|
+
const targetLayout =
|
|
572
|
+
this.renderStyle === "read" || this.renderStyle === "focused" ? "column" : "row";
|
|
573
|
+
if (this.layout !== targetLayout) {
|
|
574
|
+
this._isDefaultingLayout = true;
|
|
575
|
+
this.layout = targetLayout;
|
|
576
|
+
this.setAttribute("layout", targetLayout);
|
|
577
|
+
this._isDefaultingLayout = false;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
_clearDsdIfNeeded() {
|
|
583
|
+
if (this._hasDsd && this.shadowRoot) {
|
|
584
|
+
this.shadowRoot.replaceChildren();
|
|
585
|
+
if (this.renderOptions) {
|
|
586
|
+
delete this.renderOptions.renderBefore;
|
|
587
|
+
}
|
|
588
|
+
this._hasDsd = false;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
render() {
|
|
593
|
+
if (this._hasDsd && !this.resolvedSpec) {
|
|
594
|
+
return noChange;
|
|
595
|
+
}
|
|
596
|
+
this._clearDsdIfNeeded();
|
|
597
|
+
|
|
598
|
+
// return render(mainBodyTemplate(this), this.shadowRoot, { eventContext: this });
|
|
599
|
+
const cssLinkEl = document.querySelector(`link[href*="${this.cssFile}"]`);
|
|
600
|
+
// adding custom style for RapiDoc
|
|
601
|
+
if (cssLinkEl) {
|
|
602
|
+
this.shadowRoot.appendChild(cssLinkEl.cloneNode());
|
|
603
|
+
}
|
|
604
|
+
return mainBodyTemplate.call(this);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
get config() {
|
|
608
|
+
return {
|
|
609
|
+
renderStyle: this.renderStyle,
|
|
610
|
+
schemaStyle: this.schemaStyle,
|
|
611
|
+
defaultSchemaTab: this.defaultSchemaTab,
|
|
612
|
+
schemaExpandLevel: this.schemaExpandLevel,
|
|
613
|
+
schemaDescriptionExpanded: this.schemaDescriptionExpanded,
|
|
614
|
+
allowSchemaDescriptionExpandToggle: this.allowSchemaDescriptionExpandToggle,
|
|
615
|
+
schemaHideReadOnly: this.schemaHideReadOnly,
|
|
616
|
+
schemaHideWriteOnly: this.schemaHideWriteOnly,
|
|
617
|
+
fillRequestFieldsWithExample: this.fillRequestFieldsWithExample,
|
|
618
|
+
allowTry: this.allowTry,
|
|
619
|
+
showCurlBeforeTry: this.showCurlBeforeTry,
|
|
620
|
+
fetchCredentials: this.fetchCredentials,
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
updated(changedProperties) {
|
|
625
|
+
super.updated?.(changedProperties);
|
|
626
|
+
scheduleHighlight(this.shadowRoot);
|
|
627
|
+
|
|
628
|
+
if (changedProperties.has("theme")) {
|
|
629
|
+
if (this.theme === "light" || this.theme === "dark") {
|
|
630
|
+
this.colorScheme = this.theme;
|
|
631
|
+
this.setAttribute("color-scheme", this.colorScheme);
|
|
632
|
+
this.theme = DEFAULT_THEME;
|
|
633
|
+
this.setAttribute("theme", DEFAULT_THEME);
|
|
634
|
+
}
|
|
635
|
+
const normalizedTheme = normalizeTheme(this.theme);
|
|
636
|
+
if (this.theme !== normalizedTheme.theme) {
|
|
637
|
+
this.theme = normalizedTheme.theme;
|
|
638
|
+
this.setAttribute("theme", this.theme);
|
|
639
|
+
}
|
|
640
|
+
applyCustomTheme(this, this.theme);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (
|
|
644
|
+
changedProperties.has("primaryColor") &&
|
|
645
|
+
this.primaryColor &&
|
|
646
|
+
isValidHexColor(this.primaryColor)
|
|
647
|
+
) {
|
|
648
|
+
this.theme = normalizeTheme(this.primaryColor).theme;
|
|
649
|
+
this.setAttribute("theme", this.theme);
|
|
650
|
+
applyCustomTheme(this, this.theme);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
if (changedProperties.has("colorScheme")) {
|
|
654
|
+
if (!this.colorScheme || !"dark, light, system,".includes(`${this.colorScheme},`)) {
|
|
655
|
+
this.colorScheme = "dark";
|
|
656
|
+
}
|
|
657
|
+
if (this.getAttribute("color-scheme") !== this.colorScheme) {
|
|
658
|
+
this.setAttribute("color-scheme", this.colorScheme);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (changedProperties.has("mockServer")) {
|
|
663
|
+
if (this.mockServer === "true" && this.resolvedSpec) {
|
|
664
|
+
enableMockServer(this.resolvedSpec, {
|
|
665
|
+
statusCode: this.mockServerStatusCode,
|
|
666
|
+
statusStrategy: this.mockServerStatusStrategy,
|
|
667
|
+
delay: this.mockServerDelay,
|
|
668
|
+
log: this.mockServerLog,
|
|
669
|
+
});
|
|
670
|
+
} else if (this.mockServer !== "true") {
|
|
671
|
+
disableMockServer();
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (
|
|
675
|
+
changedProperties.has("mockServerStatusCode") ||
|
|
676
|
+
changedProperties.has("mockServerStatusStrategy") ||
|
|
677
|
+
changedProperties.has("mockServerDelay") ||
|
|
678
|
+
changedProperties.has("mockServerLog")
|
|
679
|
+
) {
|
|
680
|
+
updateMockConfig({
|
|
681
|
+
statusCode: this.mockServerStatusCode,
|
|
682
|
+
statusStrategy: this.mockServerStatusStrategy,
|
|
683
|
+
delay: this.mockServerDelay,
|
|
684
|
+
log: this.mockServerLog,
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
if (changedProperties.has("scrollContainer")) {
|
|
688
|
+
this.initIntersectionObserver();
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
observeExpandedContent() {
|
|
693
|
+
// Main Container
|
|
694
|
+
const observeOverviewEls = this.shadowRoot.querySelectorAll(".observe-me");
|
|
695
|
+
observeOverviewEls.forEach((targetEl) => {
|
|
696
|
+
this.intersectionObserver.observe(targetEl);
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
701
|
+
if (name === "spec-url") {
|
|
702
|
+
if (oldVal !== newVal) {
|
|
703
|
+
// put it at the end of event-loop to load all the attributes
|
|
704
|
+
window.setTimeout(async () => {
|
|
705
|
+
await this.loadSpec(newVal);
|
|
706
|
+
// If goto-path is provided and no location-hash is present then try to scroll there
|
|
707
|
+
if (this.gotoPath && !window.location.hash) {
|
|
708
|
+
this.scrollToPath(this.gotoPath);
|
|
709
|
+
}
|
|
710
|
+
}, 0);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
if (
|
|
714
|
+
name === "match-paths" ||
|
|
715
|
+
name === "match-type" ||
|
|
716
|
+
name === "remove-endpoints-with-badge-label-as"
|
|
717
|
+
) {
|
|
718
|
+
if (oldVal !== newVal) {
|
|
719
|
+
window.setTimeout(async () => {
|
|
720
|
+
await this.loadSpec(this.specUrl);
|
|
721
|
+
}, 0);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
if (name === "scroll-offset" && oldVal !== newVal) {
|
|
725
|
+
window.setTimeout(() => this.initIntersectionObserver(), 0);
|
|
726
|
+
}
|
|
727
|
+
if (name === "layout") {
|
|
728
|
+
if (!this._isDefaultingLayout) {
|
|
729
|
+
if (newVal !== null && "row, column,".includes(`${newVal},`)) {
|
|
730
|
+
this._userSetLayout = true;
|
|
731
|
+
this.layout = newVal;
|
|
732
|
+
} else if (newVal === null) {
|
|
733
|
+
this._userSetLayout = false;
|
|
734
|
+
const defaultLayout =
|
|
735
|
+
this.renderStyle === "read" || this.renderStyle === "focused" ? "column" : "row";
|
|
736
|
+
this._isDefaultingLayout = true;
|
|
737
|
+
this.layout = defaultLayout;
|
|
738
|
+
this.setAttribute("layout", defaultLayout);
|
|
739
|
+
this._isDefaultingLayout = false;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
if (name === "render-style") {
|
|
744
|
+
if (newVal === "read") {
|
|
745
|
+
window.setTimeout(() => {
|
|
746
|
+
this.observeExpandedContent();
|
|
747
|
+
}, 100);
|
|
748
|
+
} else {
|
|
749
|
+
this.intersectionObserver.disconnect();
|
|
750
|
+
}
|
|
751
|
+
if (!this._userSetLayout) {
|
|
752
|
+
const defaultLayout = newVal === "read" || newVal === "focused" ? "column" : "row";
|
|
753
|
+
this._isDefaultingLayout = true;
|
|
754
|
+
this.layout = defaultLayout;
|
|
755
|
+
this.setAttribute("layout", defaultLayout);
|
|
756
|
+
this._isDefaultingLayout = false;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
if (name === "api-key-name" || name === "api-key-location" || name === "api-key-value") {
|
|
760
|
+
let updateSelectedApiKey = false;
|
|
761
|
+
let apiKeyName = "";
|
|
762
|
+
let apiKeyLocation = "";
|
|
763
|
+
let apiKeyValue = "";
|
|
764
|
+
|
|
765
|
+
if (name === "api-key-name") {
|
|
766
|
+
if (this.getAttribute("api-key-location") && this.getAttribute("api-key-value")) {
|
|
767
|
+
apiKeyName = newVal;
|
|
768
|
+
apiKeyLocation = this.getAttribute("api-key-location");
|
|
769
|
+
apiKeyValue = this.getAttribute("api-key-value");
|
|
770
|
+
updateSelectedApiKey = true;
|
|
771
|
+
}
|
|
772
|
+
} else if (name === "api-key-location") {
|
|
773
|
+
if (this.getAttribute("api-key-name") && this.getAttribute("api-key-value")) {
|
|
774
|
+
apiKeyLocation = newVal;
|
|
775
|
+
apiKeyName = this.getAttribute("api-key-name");
|
|
776
|
+
apiKeyValue = this.getAttribute("api-key-value");
|
|
777
|
+
updateSelectedApiKey = true;
|
|
778
|
+
}
|
|
779
|
+
} else if (name === "api-key-value") {
|
|
780
|
+
if (this.getAttribute("api-key-name") && this.getAttribute("api-key-location")) {
|
|
781
|
+
apiKeyValue = newVal;
|
|
782
|
+
apiKeyLocation = this.getAttribute("api-key-location");
|
|
783
|
+
apiKeyName = this.getAttribute("api-key-name");
|
|
784
|
+
updateSelectedApiKey = true;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
if (updateSelectedApiKey) {
|
|
789
|
+
// While the spec is loading, resolvedSpec is a placeholder without securitySchemes
|
|
790
|
+
if (this.resolvedSpec?.securitySchemes) {
|
|
791
|
+
const rapiDocApiKey = this.resolvedSpec.securitySchemes.find(
|
|
792
|
+
(v) => v.securitySchemeId === rapidocApiKey,
|
|
793
|
+
);
|
|
794
|
+
if (!rapiDocApiKey) {
|
|
795
|
+
this.resolvedSpec.securitySchemes.push({
|
|
796
|
+
securitySchemeId: rapidocApiKey,
|
|
797
|
+
description: "api-key provided in rapidoc element attributes",
|
|
798
|
+
type: "apiKey",
|
|
799
|
+
name: apiKeyName,
|
|
800
|
+
in: apiKeyLocation,
|
|
801
|
+
value: apiKeyValue,
|
|
802
|
+
finalKeyValue: apiKeyValue,
|
|
803
|
+
});
|
|
804
|
+
} else {
|
|
805
|
+
rapiDocApiKey.name = apiKeyName;
|
|
806
|
+
rapiDocApiKey.in = apiKeyLocation;
|
|
807
|
+
rapiDocApiKey.value = apiKeyValue;
|
|
808
|
+
rapiDocApiKey.finalKeyValue = apiKeyValue;
|
|
809
|
+
}
|
|
810
|
+
this.requestUpdate();
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
super.attributeChangedCallback(name, oldVal, newVal);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
onSpecUrlChange() {
|
|
818
|
+
this.setAttribute("spec-url", this.shadowRoot.getElementById("spec-url").value);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
onSpecFileChange(e) {
|
|
822
|
+
this.setAttribute("spec-file", this.shadowRoot.getElementById("spec-file").value);
|
|
823
|
+
const specFile = e.target.files[0];
|
|
824
|
+
const reader = new FileReader();
|
|
825
|
+
reader.onload = () => {
|
|
826
|
+
try {
|
|
827
|
+
const specObj = JSON.parse(reader.result);
|
|
828
|
+
this.loadSpec(specObj);
|
|
829
|
+
this.shadowRoot.getElementById("spec-url").value = "";
|
|
830
|
+
} catch {
|
|
831
|
+
console.error("RapiDoc: Unable to read or parse json");
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
// Read the Text file
|
|
835
|
+
reader.readAsText(specFile);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
onFileLoadClick() {
|
|
839
|
+
this.shadowRoot.getElementById("spec-file").click();
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
onSearchChange(e) {
|
|
843
|
+
// The event may come from the ↩ icon (a div without `value`): read the nav-bar input instead
|
|
844
|
+
const input =
|
|
845
|
+
e.target instanceof HTMLInputElement
|
|
846
|
+
? e.target
|
|
847
|
+
: this.shadowRoot.getElementById("nav-bar-search");
|
|
848
|
+
this.searchVal = input?.value ?? "";
|
|
849
|
+
this.resolvedSpec.tags.forEach((tag) =>
|
|
850
|
+
tag.paths.filter((path) => {
|
|
851
|
+
if (this.searchVal) {
|
|
852
|
+
if (getMatchedPaths(this.searchVal, path, tag.name)) {
|
|
853
|
+
tag.expanded = true;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}),
|
|
857
|
+
);
|
|
858
|
+
this.resolvedSpec.components.forEach((component) =>
|
|
859
|
+
component.subComponents.filter((v) => {
|
|
860
|
+
v.expanded = false;
|
|
861
|
+
if (getMatchedComponents(this.searchVal, v)) {
|
|
862
|
+
v.expanded = true;
|
|
863
|
+
}
|
|
864
|
+
}),
|
|
865
|
+
);
|
|
866
|
+
this.requestUpdate();
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
onClearSearch() {
|
|
870
|
+
const searchEl = this.shadowRoot.getElementById("nav-bar-search");
|
|
871
|
+
searchEl.value = "";
|
|
872
|
+
this.searchVal = "";
|
|
873
|
+
this.resolvedSpec.components.forEach((component) =>
|
|
874
|
+
component.subComponents.filter((v) => {
|
|
875
|
+
v.expanded = true;
|
|
876
|
+
}),
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
onOpenNavBarToggle() {
|
|
881
|
+
const navBarEL = this.shadowRoot.getElementById("nav-bar");
|
|
882
|
+
navBarEL.classList.toggle("floating-nav");
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
onShowAdvancedSearchClicked() {
|
|
886
|
+
this.shadowRoot.getElementById("advanced-search-dialog").showModal();
|
|
887
|
+
}
|
|
888
|
+
onAdvancedSearchClose() {
|
|
889
|
+
this.shadowRoot.getElementById("advanced-search-dialog").close();
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// Public Method
|
|
893
|
+
async loadSpec(specUrl) {
|
|
894
|
+
if (!specUrl) {
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
this.searchVal = "";
|
|
898
|
+
try {
|
|
899
|
+
if (!this._hasDsd) {
|
|
900
|
+
this.resolvedSpec = {
|
|
901
|
+
specLoadError: false,
|
|
902
|
+
isSpecLoading: true,
|
|
903
|
+
tags: [],
|
|
904
|
+
};
|
|
905
|
+
this.loading = true;
|
|
906
|
+
}
|
|
907
|
+
this.loadFailed = false;
|
|
908
|
+
const spec = await ProcessSpec.call(
|
|
909
|
+
this,
|
|
910
|
+
specUrl,
|
|
911
|
+
this.generateMissingTags === "true",
|
|
912
|
+
this.sortTags === "true",
|
|
913
|
+
this.sortSchemas === "true",
|
|
914
|
+
this.getAttribute("sort-endpoints-by"),
|
|
915
|
+
this.getAttribute("api-key-name"),
|
|
916
|
+
this.getAttribute("api-key-location"),
|
|
917
|
+
this.getAttribute("api-key-value"),
|
|
918
|
+
this.getAttribute("server-url"),
|
|
919
|
+
this.matchPaths,
|
|
920
|
+
this.matchType,
|
|
921
|
+
this.removeEndpointsWithBadgeLabelAs,
|
|
922
|
+
);
|
|
923
|
+
this.loading = false;
|
|
924
|
+
this.afterSpecParsedAndValidated(spec);
|
|
925
|
+
} catch (err) {
|
|
926
|
+
this.loading = false;
|
|
927
|
+
this._clearDsdIfNeeded();
|
|
928
|
+
this.loadFailed = true;
|
|
929
|
+
this.resolvedSpec = null;
|
|
930
|
+
console.error(`RapiDoc: Unable to resolve the API spec.. ${err.message}`);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
async afterSpecParsedAndValidated(spec) {
|
|
935
|
+
this._clearDsdIfNeeded();
|
|
936
|
+
this.resolvedSpec = spec;
|
|
937
|
+
this.selectedServer = undefined;
|
|
938
|
+
if (this.defaultApiServerUrl) {
|
|
939
|
+
if (this.defaultApiServerUrl === this.serverUrl) {
|
|
940
|
+
this.selectedServer = {
|
|
941
|
+
url: this.serverUrl,
|
|
942
|
+
computedUrl: this.serverUrl,
|
|
943
|
+
};
|
|
944
|
+
} else if (this.resolvedSpec.servers) {
|
|
945
|
+
this.selectedServer = this.resolvedSpec.servers.find(
|
|
946
|
+
(v) => v.url === this.defaultApiServerUrl,
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
if (!this.selectedServer) {
|
|
951
|
+
if (this.resolvedSpec.servers) {
|
|
952
|
+
this.selectedServer = this.resolvedSpec.servers[0];
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
this.requestUpdate();
|
|
956
|
+
while (!(await this.updateComplete));
|
|
957
|
+
const specLoadedEvent = new CustomEvent("spec-loaded", { detail: spec });
|
|
958
|
+
this.dispatchEvent(specLoadedEvent);
|
|
959
|
+
|
|
960
|
+
if (this.mockServer === "true") {
|
|
961
|
+
enableMockServer(this.resolvedSpec, {
|
|
962
|
+
statusCode: this.mockServerStatusCode,
|
|
963
|
+
statusStrategy: this.mockServerStatusStrategy,
|
|
964
|
+
delay: this.mockServerDelay,
|
|
965
|
+
log: this.mockServerLog,
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// Initiate IntersectionObserver and put it at the end of event loop, to allow loading all the child elements (must for larger specs)
|
|
970
|
+
this.intersectionObserver.disconnect();
|
|
971
|
+
if (this._intersectingElements) {
|
|
972
|
+
this._intersectingElements.clear();
|
|
973
|
+
}
|
|
974
|
+
if (this.renderStyle === "read") {
|
|
975
|
+
await sleep(100);
|
|
976
|
+
this.observeExpandedContent(); // This will auto-highlight the selected nav-item in read-mode
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
this.isIntersectionObserverActive = true;
|
|
980
|
+
|
|
981
|
+
// On first time Spec load, try to navigate to location hash if provided
|
|
982
|
+
const elementId = this.getElementIDFromURL();
|
|
983
|
+
|
|
984
|
+
if (elementId) {
|
|
985
|
+
if (this.renderStyle === "view") {
|
|
986
|
+
this.expandAndGotoOperation(elementId, true, true);
|
|
987
|
+
} else {
|
|
988
|
+
this.scrollToPath(elementId);
|
|
989
|
+
}
|
|
990
|
+
} else if (this.gotoPath) {
|
|
991
|
+
this.scrollToPath(this.gotoPath);
|
|
992
|
+
} else if (this.renderStyle === "focused") {
|
|
993
|
+
// If goto-path is provided and no location-hash is present then try to scroll to default element
|
|
994
|
+
if (!this.gotoPath) {
|
|
995
|
+
const defaultElementId =
|
|
996
|
+
this.showInfo === "true" ? "overview" : this.resolvedSpec.tags[0]?.paths[0]?.elementId;
|
|
997
|
+
if (defaultElementId) {
|
|
998
|
+
this.scrollToPath(defaultElementId);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* Return the URL from where is served the RapiDoc component, removing any hash
|
|
1006
|
+
*/
|
|
1007
|
+
getComponentBaseURL() {
|
|
1008
|
+
return window.location.href.split("#")[0];
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/**
|
|
1012
|
+
* From the URL return the ID of the element from the location hash
|
|
1013
|
+
*/
|
|
1014
|
+
getElementIDFromURL() {
|
|
1015
|
+
return window.location.hash ? window.location.hash.substring(1) : "";
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
replaceHistoryState(hashId) {
|
|
1019
|
+
const baseURL = this.getComponentBaseURL();
|
|
1020
|
+
window.history.replaceState(null, null, hashId ? `${baseURL}#${hashId}` : baseURL);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
expandAndGotoOperation(elementId, scrollToElement = true) {
|
|
1024
|
+
if (!this.resolvedSpec) {
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
// Expand full operation and tag
|
|
1028
|
+
let isExpandingNeeded = true;
|
|
1029
|
+
const tmpElementId = elementId.indexOf("#") === -1 ? elementId : elementId.substring(1);
|
|
1030
|
+
if (
|
|
1031
|
+
tmpElementId.startsWith("overview") ||
|
|
1032
|
+
tmpElementId === "servers" ||
|
|
1033
|
+
tmpElementId === "auth"
|
|
1034
|
+
) {
|
|
1035
|
+
isExpandingNeeded = false;
|
|
1036
|
+
} else {
|
|
1037
|
+
for (let i = 0; i < this.resolvedSpec.tags?.length; i++) {
|
|
1038
|
+
const tag = this.resolvedSpec.tags[i];
|
|
1039
|
+
const path = tag.paths?.find((p) => p.elementId === elementId);
|
|
1040
|
+
if (path) {
|
|
1041
|
+
if (path.expanded && tag.expanded) {
|
|
1042
|
+
isExpandingNeeded = false;
|
|
1043
|
+
} else {
|
|
1044
|
+
path.expanded = true;
|
|
1045
|
+
tag.expanded = true;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
if (this.resolvedSpec.tagGroups) {
|
|
1050
|
+
for (let g = 0; g < this.resolvedSpec.tagGroups.length; g++) {
|
|
1051
|
+
const group = this.resolvedSpec.tagGroups[g];
|
|
1052
|
+
const hasPath = group.tags?.some((t) => t.paths?.some((p) => p.elementId === elementId));
|
|
1053
|
+
if (hasPath && !group.expanded) {
|
|
1054
|
+
group.expanded = true;
|
|
1055
|
+
isExpandingNeeded = true;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
if (scrollToElement) {
|
|
1061
|
+
// requestUpdate() and delay required, else we cant find element
|
|
1062
|
+
if (isExpandingNeeded) {
|
|
1063
|
+
this.requestUpdate();
|
|
1064
|
+
}
|
|
1065
|
+
window.setTimeout(
|
|
1066
|
+
() => {
|
|
1067
|
+
const firstNavEl = this.shadowRoot.querySelector(".first-nav-item");
|
|
1068
|
+
const isFirst = firstNavEl && firstNavEl.dataset.contentId === tmpElementId;
|
|
1069
|
+
const gotoEl = this.shadowRoot.getElementById(tmpElementId);
|
|
1070
|
+
if (gotoEl) {
|
|
1071
|
+
const mainContentEl = this.shadowRoot.querySelector(".main-content");
|
|
1072
|
+
let targetTop: number;
|
|
1073
|
+
if (this.autoHeight === "true") {
|
|
1074
|
+
this.scrollAutoHeightTarget(gotoEl, tmpElementId);
|
|
1075
|
+
} else if (isFirst) {
|
|
1076
|
+
targetTop = 0;
|
|
1077
|
+
this.lockObserverDuringScroll(gotoEl, tmpElementId, targetTop);
|
|
1078
|
+
if (mainContentEl) {
|
|
1079
|
+
mainContentEl.scrollTo({ top: 0, behavior: this.getContentScrollBehavior() });
|
|
1080
|
+
} else {
|
|
1081
|
+
gotoEl.scrollIntoView({
|
|
1082
|
+
behavior: this.getContentScrollBehavior(),
|
|
1083
|
+
block: "start",
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
} else if (mainContentEl) {
|
|
1087
|
+
const containerRect = mainContentEl.getBoundingClientRect();
|
|
1088
|
+
const targetRect = gotoEl.getBoundingClientRect();
|
|
1089
|
+
targetTop = targetRect.top - containerRect.top + mainContentEl.scrollTop;
|
|
1090
|
+
this.lockObserverDuringScroll(gotoEl, tmpElementId, targetTop);
|
|
1091
|
+
mainContentEl.scrollTo({ top: targetTop, behavior: this.getContentScrollBehavior() });
|
|
1092
|
+
} else {
|
|
1093
|
+
this.lockObserverDuringScroll(gotoEl, tmpElementId);
|
|
1094
|
+
gotoEl.scrollIntoView({ behavior: this.getContentScrollBehavior(), block: "start" });
|
|
1095
|
+
}
|
|
1096
|
+
this._activeElementId = tmpElementId;
|
|
1097
|
+
this.dispatchEvent(
|
|
1098
|
+
new CustomEvent("nav-active-change", {
|
|
1099
|
+
detail: {
|
|
1100
|
+
id: tmpElementId,
|
|
1101
|
+
elementId: tmpElementId,
|
|
1102
|
+
},
|
|
1103
|
+
bubbles: true,
|
|
1104
|
+
composed: true,
|
|
1105
|
+
}),
|
|
1106
|
+
);
|
|
1107
|
+
if (this.updateRoute === "true") {
|
|
1108
|
+
this.replaceHistoryState(tmpElementId);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
},
|
|
1112
|
+
isExpandingNeeded ? 150 : 0,
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
isValidTopId(id) {
|
|
1118
|
+
return id.startsWith("overview") || id === "servers" || id === "auth";
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
isValidPathId(id) {
|
|
1122
|
+
if (id === "overview" && this.showInfo) {
|
|
1123
|
+
return true;
|
|
1124
|
+
}
|
|
1125
|
+
if (id === "servers" && this.allowServerSelection) {
|
|
1126
|
+
return true;
|
|
1127
|
+
}
|
|
1128
|
+
if (id === "auth" && this.allowAuthentication) {
|
|
1129
|
+
return true;
|
|
1130
|
+
}
|
|
1131
|
+
if (id.startsWith("tag--")) {
|
|
1132
|
+
return this.resolvedSpec?.tags?.find((tag) => tag.elementId === id);
|
|
1133
|
+
}
|
|
1134
|
+
return this.resolvedSpec?.tags?.find((tag) => tag.paths.find((path) => path.elementId === id));
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
initIntersectionObserver() {
|
|
1138
|
+
if (this.intersectionObserver) {
|
|
1139
|
+
this.intersectionObserver.disconnect();
|
|
1140
|
+
}
|
|
1141
|
+
let container = null;
|
|
1142
|
+
if (this.scrollContainer && typeof document !== "undefined") {
|
|
1143
|
+
try {
|
|
1144
|
+
container = document.querySelector(this.scrollContainer);
|
|
1145
|
+
const isAncestor = Boolean(
|
|
1146
|
+
container &&
|
|
1147
|
+
(container.contains(this) ||
|
|
1148
|
+
(this.assignedSlot && container.contains(this.assignedSlot)) ||
|
|
1149
|
+
(container.shadowRoot && container.shadowRoot.contains(this.assignedSlot))),
|
|
1150
|
+
);
|
|
1151
|
+
if (container && !isAncestor) {
|
|
1152
|
+
// If the specified element is not an ancestor of <rapi-doc>, IntersectionObserver cannot observe inside it
|
|
1153
|
+
console.warn(
|
|
1154
|
+
`RapiDoc: The element specified by scroll-container ("${this.scrollContainer}") is not an ancestor of <rapi-doc>. Falling back to auto-detection.`,
|
|
1155
|
+
);
|
|
1156
|
+
container = null;
|
|
1157
|
+
}
|
|
1158
|
+
} catch {
|
|
1159
|
+
// Ignore invalid query selector
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
if (!container && typeof window !== "undefined") {
|
|
1163
|
+
let parent: any = this.assignedSlot ? this.assignedSlot.parentElement : this.parentElement;
|
|
1164
|
+
while (parent && parent !== document.body && parent !== document.documentElement) {
|
|
1165
|
+
const overflowY = window.getComputedStyle(parent).overflowY;
|
|
1166
|
+
if (overflowY === "auto" || overflowY === "scroll") {
|
|
1167
|
+
container = parent;
|
|
1168
|
+
break;
|
|
1169
|
+
}
|
|
1170
|
+
parent =
|
|
1171
|
+
parent.parentElement ||
|
|
1172
|
+
(parent.getRootNode && parent.getRootNode() instanceof ShadowRoot
|
|
1173
|
+
? (parent.getRootNode() as ShadowRoot).host
|
|
1174
|
+
: null);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
this._scrollRootContainer = container;
|
|
1178
|
+
const intersectionObserverOptions = {
|
|
1179
|
+
root: container,
|
|
1180
|
+
// Headings that slide under a sticky page header (getScrollOffset) count as scrolled past.
|
|
1181
|
+
rootMargin: `-${this.getScrollOffset()}px 0px -50% 0px`,
|
|
1182
|
+
threshold: 0,
|
|
1183
|
+
};
|
|
1184
|
+
this.intersectionObserver = new IntersectionObserver((entries) => {
|
|
1185
|
+
this.onIntersect(entries);
|
|
1186
|
+
}, intersectionObserverOptions);
|
|
1187
|
+
if (this.isIntersectionObserverActive) {
|
|
1188
|
+
this.observeExpandedContent();
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
onIntersect(entries) {
|
|
1193
|
+
if (this.isIntersectionObserverActive === false) {
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
if (!this._intersectingElements) {
|
|
1198
|
+
this._intersectingElements = new Map();
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
for (const entry of entries) {
|
|
1202
|
+
if (entry.isIntersecting && entry.intersectionRatio > 0) {
|
|
1203
|
+
this._intersectingElements.set(entry.target.id, entry.target);
|
|
1204
|
+
} else {
|
|
1205
|
+
this._intersectingElements.delete(entry.target.id);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
if (this._intersectingElements.size === 0) {
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
const activeCandidates = Array.from(this._intersectingElements.values()).filter(
|
|
1214
|
+
(el) => el.isConnected,
|
|
1215
|
+
);
|
|
1216
|
+
if (activeCandidates.length === 0) {
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
const containerTop =
|
|
1221
|
+
(this._scrollRootContainer ? this._scrollRootContainer.getBoundingClientRect().top : 0) +
|
|
1222
|
+
this.getScrollOffset();
|
|
1223
|
+
const visibleHeadings = [];
|
|
1224
|
+
const scrolledPast = [];
|
|
1225
|
+
|
|
1226
|
+
for (const el of activeCandidates) {
|
|
1227
|
+
const top = el.getBoundingClientRect().top - containerTop;
|
|
1228
|
+
// A section scrolled to (scrollToPath) can end up a fraction of a pixel above the top: the
|
|
1229
|
+
// browser rounds the scroll position to device pixels. It still counts as visible.
|
|
1230
|
+
if (top > -1) {
|
|
1231
|
+
visibleHeadings.push({ el, top });
|
|
1232
|
+
} else {
|
|
1233
|
+
scrolledPast.push({ el, top });
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
let bestEl;
|
|
1238
|
+
if (visibleHeadings.length > 0) {
|
|
1239
|
+
// Preference to the topmost heading visible in the viewport/reading zone
|
|
1240
|
+
visibleHeadings.sort((a, b) => a.top - b.top);
|
|
1241
|
+
bestEl = visibleHeadings[0].el;
|
|
1242
|
+
} else {
|
|
1243
|
+
// If all visible candidates have their headings scrolled past top (< 0),
|
|
1244
|
+
// pick the one whose top is closest to 0 (the section currently filling the reading area)
|
|
1245
|
+
scrolledPast.sort((a, b) => b.top - a.top);
|
|
1246
|
+
bestEl = scrolledPast[0].el;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
const targetId = bestEl.id;
|
|
1250
|
+
if (this._activeElementId === targetId) {
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
this._activeElementId = targetId;
|
|
1254
|
+
|
|
1255
|
+
if (this._intersectionRaf) {
|
|
1256
|
+
cancelAnimationFrame(this._intersectionRaf);
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
this._intersectionRaf = requestAnimationFrame(() => {
|
|
1260
|
+
// 1. Dispatch custom event so outer portals/host apps can sync active state
|
|
1261
|
+
let resolvedOperationId = targetId;
|
|
1262
|
+
if (targetId.startsWith("tag--") && this.resolvedSpec?.tags) {
|
|
1263
|
+
const matchingTag = this.resolvedSpec.tags.find((t) => t.elementId === targetId);
|
|
1264
|
+
if (matchingTag?.paths?.[0]?.elementId) {
|
|
1265
|
+
resolvedOperationId = matchingTag.paths[0].elementId;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
this.dispatchEvent(
|
|
1270
|
+
new CustomEvent("nav-active-change", {
|
|
1271
|
+
detail: {
|
|
1272
|
+
id: targetId,
|
|
1273
|
+
elementId: targetId,
|
|
1274
|
+
operationId: resolvedOperationId,
|
|
1275
|
+
},
|
|
1276
|
+
bubbles: true,
|
|
1277
|
+
composed: true,
|
|
1278
|
+
}),
|
|
1279
|
+
);
|
|
1280
|
+
|
|
1281
|
+
if (this.updateRoute === "true") {
|
|
1282
|
+
this.replaceHistoryState(targetId);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// 2. If internal navbar is active (show-side-nav="true"), update Shadow DOM elements
|
|
1286
|
+
const newNavEl = this.shadowRoot.getElementById(`link-${targetId}`);
|
|
1287
|
+
if (newNavEl) {
|
|
1288
|
+
const oldNavEl = this.shadowRoot.querySelector(
|
|
1289
|
+
".nav-bar-tag.active, .nav-bar-path.active, .nav-bar-info.active, .nav-bar-h1.active, .nav-bar-h2.active, .operations.active",
|
|
1290
|
+
);
|
|
1291
|
+
|
|
1292
|
+
if (oldNavEl && oldNavEl !== newNavEl) {
|
|
1293
|
+
oldNavEl.classList.remove("active");
|
|
1294
|
+
oldNavEl.part.remove("section-navbar-active-item");
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
expandParentNavContainers(newNavEl);
|
|
1298
|
+
newNavEl.classList.add("active");
|
|
1299
|
+
newNavEl.part.add("section-navbar-active-item");
|
|
1300
|
+
|
|
1301
|
+
// Scroll sidebar only if the active item is out of view
|
|
1302
|
+
const navScrollEl = newNavEl.closest(".nav-scroll");
|
|
1303
|
+
if (navScrollEl) {
|
|
1304
|
+
const headerEl =
|
|
1305
|
+
navScrollEl.querySelector(".nav-bar-header") ||
|
|
1306
|
+
navScrollEl.querySelector(".nav-bar-search-container");
|
|
1307
|
+
const topOffset = headerEl ? headerEl.offsetHeight : 0;
|
|
1308
|
+
|
|
1309
|
+
if (newNavEl.offsetTop <= topOffset + 30) {
|
|
1310
|
+
if (navScrollEl.scrollTop > 0) {
|
|
1311
|
+
const behavior = this.scrollBehavior === "auto" ? "auto" : "smooth";
|
|
1312
|
+
navScrollEl.scrollTo({ top: 0, behavior });
|
|
1313
|
+
}
|
|
1314
|
+
} else {
|
|
1315
|
+
const scrollRect = navScrollEl.getBoundingClientRect();
|
|
1316
|
+
const itemRect = newNavEl.getBoundingClientRect();
|
|
1317
|
+
const isOutOfView =
|
|
1318
|
+
itemRect.top < scrollRect.top + topOffset + 15 ||
|
|
1319
|
+
itemRect.bottom > scrollRect.bottom - 40;
|
|
1320
|
+
if (isOutOfView) {
|
|
1321
|
+
const behavior = this.scrollBehavior === "auto" ? "auto" : "smooth";
|
|
1322
|
+
// Land the item below the sticky search header (which sits inside the scroll area), not under it
|
|
1323
|
+
const targetTop =
|
|
1324
|
+
itemRect.top - scrollRect.top + navScrollEl.scrollTop - topOffset - 20;
|
|
1325
|
+
navScrollEl.scrollTo({ top: targetTop, behavior });
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// Called by anchor tags created using markdown
|
|
1334
|
+
handleHref(e) {
|
|
1335
|
+
if (e.target.tagName.toLowerCase() === "a") {
|
|
1336
|
+
if (e.target.getAttribute("href").startsWith("#")) {
|
|
1337
|
+
const gotoEl = this.shadowRoot.getElementById(
|
|
1338
|
+
e.target.getAttribute("href").replace("#", ""),
|
|
1339
|
+
);
|
|
1340
|
+
if (gotoEl) {
|
|
1341
|
+
const mainContentEl = this.shadowRoot.querySelector(".main-content");
|
|
1342
|
+
if (this.autoHeight === "true" || this.hasAttribute("auto-height")) {
|
|
1343
|
+
this.scrollAutoHeightTarget(gotoEl, gotoEl.id);
|
|
1344
|
+
} else if (mainContentEl) {
|
|
1345
|
+
const containerRect = mainContentEl.getBoundingClientRect();
|
|
1346
|
+
const targetRect = gotoEl.getBoundingClientRect();
|
|
1347
|
+
const targetTop = targetRect.top - containerRect.top + mainContentEl.scrollTop;
|
|
1348
|
+
mainContentEl.scrollTo({ top: targetTop, behavior: this.scrollBehavior });
|
|
1349
|
+
} else {
|
|
1350
|
+
gotoEl.scrollIntoView({ behavior: this.scrollBehavior, block: "start" });
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
/**
|
|
1358
|
+
* Height of the area at the top of the scroll root that sticky or fixed page headers cover, in px.
|
|
1359
|
+
* Only applies with auto-height, where the page (or `scroll-container`) scrolls instead of RapiDoc.
|
|
1360
|
+
* Uses the `scroll-offset` attribute when set, otherwise the scroll root's `scroll-padding-top`,
|
|
1361
|
+
* so pages that already reserve space for their header need no extra configuration.
|
|
1362
|
+
*/
|
|
1363
|
+
getScrollOffset(): number {
|
|
1364
|
+
if (
|
|
1365
|
+
!(this.autoHeight === "true" || this.hasAttribute("auto-height")) ||
|
|
1366
|
+
typeof window === "undefined"
|
|
1367
|
+
) {
|
|
1368
|
+
return 0;
|
|
1369
|
+
}
|
|
1370
|
+
const explicit = parseFloat(this.scrollOffset);
|
|
1371
|
+
if (Number.isFinite(explicit)) {
|
|
1372
|
+
return explicit;
|
|
1373
|
+
}
|
|
1374
|
+
const root: HTMLElement =
|
|
1375
|
+
this._scrollRootContainer ||
|
|
1376
|
+
(document.scrollingElement as HTMLElement) ||
|
|
1377
|
+
document.documentElement;
|
|
1378
|
+
const padding = getComputedStyle(root).scrollPaddingTop;
|
|
1379
|
+
const value = parseFloat(padding);
|
|
1380
|
+
if (!Number.isFinite(value)) {
|
|
1381
|
+
return 0;
|
|
1382
|
+
}
|
|
1383
|
+
return padding.endsWith("%") ? (value / 100) * root.clientHeight : value;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
/**
|
|
1387
|
+
* auto-height: scrolls the page, or the `scroll-container`, so that `targetEl` starts just below
|
|
1388
|
+
* the area covered by sticky headers (see getScrollOffset).
|
|
1389
|
+
*/
|
|
1390
|
+
scrollAutoHeightTarget(targetEl: HTMLElement, targetId: string) {
|
|
1391
|
+
const top = targetEl.getBoundingClientRect().top - this.getScrollOffset();
|
|
1392
|
+
const container = this._scrollRootContainer;
|
|
1393
|
+
if (container) {
|
|
1394
|
+
const targetTop = top - container.getBoundingClientRect().top + container.scrollTop;
|
|
1395
|
+
this.lockObserverDuringScroll(targetEl, targetId, targetTop);
|
|
1396
|
+
container.scrollTo({ top: targetTop, behavior: this.getContentScrollBehavior() });
|
|
1397
|
+
} else {
|
|
1398
|
+
const targetTop = top + window.scrollY;
|
|
1399
|
+
this.lockObserverDuringScroll(targetEl, targetId, targetTop);
|
|
1400
|
+
window.scrollTo({ top: targetTop, behavior: this.getContentScrollBehavior() });
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
/**
|
|
1405
|
+
* Temporarily deactivates the IntersectionObserver during navigation/programmatic scrolling,
|
|
1406
|
+
* preventing intermediate elements from hijacking the active navbar item while smooth scrolling.
|
|
1407
|
+
* Re-activates the observer once scrolling finishes (via 'scrollend', debounced scroll fallback, or safety timeout).
|
|
1408
|
+
*
|
|
1409
|
+
* @param {HTMLElement | null} targetEl - The element being scrolled to
|
|
1410
|
+
* @param {string} targetId - Element / operation identifier
|
|
1411
|
+
* @param {number} [targetTop] - Target scroll offset if known
|
|
1412
|
+
*/
|
|
1413
|
+
lockObserverDuringScroll(
|
|
1414
|
+
targetEl: HTMLElement | null,
|
|
1415
|
+
targetId: string = "",
|
|
1416
|
+
targetTop?: number,
|
|
1417
|
+
) {
|
|
1418
|
+
if (this._intersectionRaf) {
|
|
1419
|
+
cancelAnimationFrame(this._intersectionRaf);
|
|
1420
|
+
this._intersectionRaf = null;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
if (typeof this._observerLockCleanup === "function") {
|
|
1424
|
+
this._observerLockCleanup();
|
|
1425
|
+
this._observerLockCleanup = null;
|
|
1426
|
+
}
|
|
1427
|
+
if (this._observerLockTimeout) {
|
|
1428
|
+
clearTimeout(this._observerLockTimeout);
|
|
1429
|
+
this._observerLockTimeout = null;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
// onIntersect tracks elements by their DOM id; targetId can be a shorter slug of the same element
|
|
1433
|
+
// (e.g. "get-route-estimate" for "route-planning--get-route-estimate"). Keying by the slug would leave
|
|
1434
|
+
// an entry the observer never removes, pinning the active item to this element.
|
|
1435
|
+
const observedId = targetEl?.id || targetId;
|
|
1436
|
+
this.isIntersectionObserverActive = false;
|
|
1437
|
+
if (this._intersectingElements) {
|
|
1438
|
+
this._intersectingElements.clear();
|
|
1439
|
+
if (targetEl && observedId) {
|
|
1440
|
+
this._intersectingElements.set(observedId, targetEl);
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
if (observedId) {
|
|
1444
|
+
this._activeElementId = observedId;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
const isAutoHeight = this.autoHeight === "true" || this.hasAttribute("auto-height");
|
|
1448
|
+
const scrollContainer: any = isAutoHeight
|
|
1449
|
+
? this._scrollRootContainer || (typeof window !== "undefined" ? window : null)
|
|
1450
|
+
: this.shadowRoot?.querySelector(".main-content") ||
|
|
1451
|
+
(typeof window !== "undefined" ? window : null);
|
|
1452
|
+
|
|
1453
|
+
if (!scrollContainer || typeof window === "undefined") {
|
|
1454
|
+
this.isIntersectionObserverActive = true;
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
let isUnlocked = false;
|
|
1459
|
+
const unlock = () => {
|
|
1460
|
+
if (isUnlocked) return;
|
|
1461
|
+
isUnlocked = true;
|
|
1462
|
+
if (cleanup) {
|
|
1463
|
+
cleanup();
|
|
1464
|
+
cleanup = null;
|
|
1465
|
+
this._observerLockCleanup = null;
|
|
1466
|
+
}
|
|
1467
|
+
if (this._observerLockTimeout) {
|
|
1468
|
+
clearTimeout(this._observerLockTimeout);
|
|
1469
|
+
this._observerLockTimeout = null;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
if (targetEl && observedId && this._intersectingElements) {
|
|
1473
|
+
this._intersectingElements.set(observedId, targetEl);
|
|
1474
|
+
}
|
|
1475
|
+
this.isIntersectionObserverActive = true;
|
|
1476
|
+
};
|
|
1477
|
+
|
|
1478
|
+
let debounceTimer: any = null;
|
|
1479
|
+
const onScroll = () => {
|
|
1480
|
+
clearTimeout(debounceTimer);
|
|
1481
|
+
debounceTimer = setTimeout(unlock, 120);
|
|
1482
|
+
};
|
|
1483
|
+
|
|
1484
|
+
const onScrollEnd = () => {
|
|
1485
|
+
unlock();
|
|
1486
|
+
};
|
|
1487
|
+
|
|
1488
|
+
let cleanup: any = () => {
|
|
1489
|
+
scrollContainer.removeEventListener?.("scrollend", onScrollEnd);
|
|
1490
|
+
window.removeEventListener?.("scrollend", onScrollEnd);
|
|
1491
|
+
scrollContainer.removeEventListener?.("scroll", onScroll);
|
|
1492
|
+
window.removeEventListener?.("scroll", onScroll);
|
|
1493
|
+
clearTimeout(debounceTimer);
|
|
1494
|
+
};
|
|
1495
|
+
this._observerLockCleanup = cleanup;
|
|
1496
|
+
|
|
1497
|
+
if ("onscrollend" in window) {
|
|
1498
|
+
scrollContainer.addEventListener?.("scrollend", onScrollEnd, { once: true, passive: true });
|
|
1499
|
+
window.addEventListener?.("scrollend", onScrollEnd, { once: true, passive: true });
|
|
1500
|
+
}
|
|
1501
|
+
scrollContainer.addEventListener?.("scroll", onScroll, { passive: true });
|
|
1502
|
+
window.addEventListener?.("scroll", onScroll, { passive: true });
|
|
1503
|
+
|
|
1504
|
+
const currentScrollTop =
|
|
1505
|
+
scrollContainer === window ? window.scrollY : scrollContainer.scrollTop || 0;
|
|
1506
|
+
const isZeroDistance =
|
|
1507
|
+
typeof targetTop === "number" && Math.abs(targetTop - currentScrollTop) < 3;
|
|
1508
|
+
const maxSafetyDelay = isZeroDistance
|
|
1509
|
+
? 60
|
|
1510
|
+
: this.getContentScrollBehavior() === "smooth"
|
|
1511
|
+
? 1200
|
|
1512
|
+
: 100;
|
|
1513
|
+
this._observerLockTimeout = setTimeout(unlock, maxSafetyDelay);
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
/**
|
|
1517
|
+
* Called by
|
|
1518
|
+
* - onClick of Navigation Bar
|
|
1519
|
+
* - onClick of Advanced Search items
|
|
1520
|
+
*
|
|
1521
|
+
* Functionality:
|
|
1522
|
+
* 1. First deactivate IntersectionObserver
|
|
1523
|
+
* 2. Scroll to the element
|
|
1524
|
+
* 3. Activate IntersectionObserver (after scroll completes)
|
|
1525
|
+
*/
|
|
1526
|
+
async scrollToEventTarget(event, scrollNavItemToView = true) {
|
|
1527
|
+
if (!(event.type === "click" || (event.type === "keyup" && event.keyCode === 13))) {
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
const navEl = ((event.target as HTMLElement)?.closest?.("[data-content-id]") ||
|
|
1531
|
+
event.target) as HTMLElement;
|
|
1532
|
+
if (!navEl?.dataset?.contentId) {
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
if (this.renderStyle === "focused") {
|
|
1536
|
+
const requestEl = this.shadowRoot.querySelector("api-request");
|
|
1537
|
+
if (typeof requestEl?.beforeNavigationFocusedMode === "function") {
|
|
1538
|
+
requestEl.beforeNavigationFocusedMode();
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
const isFirstNavItem = navEl.dataset.firstNavItem === "true";
|
|
1542
|
+
this.scrollToPath(navEl.dataset.contentId, true, scrollNavItemToView, isFirstNavItem);
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// Public Method - Returns the DOM element in ShadowRoot matching elementId, operationId, or slug
|
|
1546
|
+
getTargetElement(elementId: string): HTMLElement | null {
|
|
1547
|
+
if (!elementId || !this.shadowRoot) return null;
|
|
1548
|
+
return (this.shadowRoot.getElementById(elementId) ||
|
|
1549
|
+
this.shadowRoot.querySelector(`[data-op-slug="${CSS.escape(elementId)}"]`) ||
|
|
1550
|
+
this.shadowRoot.querySelector(`[id$="--${CSS.escape(elementId)}"]`) ||
|
|
1551
|
+
this.shadowRoot.getElementById(
|
|
1552
|
+
elementId.replace(/^[a-z0-9_-]+--/, ""),
|
|
1553
|
+
)) as HTMLElement | null;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
/**
|
|
1557
|
+
* Every operation can also be reached by its method + path anchor (methodPathSlug, e.g.
|
|
1558
|
+
* "get-routes-id-estimate"), which stays the same when its operationId or summary changes. The
|
|
1559
|
+
* comparison ignores case and punctuation, so older RapiDoc anchors ("get-/routes/-id-/estimate") work too.
|
|
1560
|
+
* Returns the operation's own element id for such an anchor, otherwise elementId unchanged.
|
|
1561
|
+
*/
|
|
1562
|
+
resolveOperationAlias(elementId: string): string {
|
|
1563
|
+
if (!elementId || this.getTargetElement(elementId)) return elementId;
|
|
1564
|
+
const normalize = (id: string) =>
|
|
1565
|
+
id
|
|
1566
|
+
.toLowerCase()
|
|
1567
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
1568
|
+
.replace(/^-+|-+$/g, "");
|
|
1569
|
+
const wanted = normalize(elementId);
|
|
1570
|
+
for (const tag of this.resolvedSpec?.tags || []) {
|
|
1571
|
+
const op = tag.paths?.find((p) => normalize(methodPathSlug(p.method, p.path)) === wanted);
|
|
1572
|
+
if (op) return op.elementId;
|
|
1573
|
+
}
|
|
1574
|
+
return elementId;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
// Public Method - Binds an external navbar to this <rapi-doc> element
|
|
1578
|
+
bindNavBar(sidebar: HTMLElement | string, options?: any) {
|
|
1579
|
+
return bindNavBar(sidebar, this, options);
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
// Public Method (scrolls to a given path and highlights the left-nav selection)
|
|
1583
|
+
async scrollTo(elementId) {
|
|
1584
|
+
return this.scrollToPath(elementId);
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
// Public Method (scrolls to a given path and highlights the left-nav selection)
|
|
1588
|
+
async scrollToPath(
|
|
1589
|
+
elementId,
|
|
1590
|
+
expandPath = true,
|
|
1591
|
+
scrollNavItemToView = true,
|
|
1592
|
+
isFirstNavItem = false,
|
|
1593
|
+
) {
|
|
1594
|
+
elementId = this.resolveOperationAlias(elementId);
|
|
1595
|
+
if (this.renderStyle === "focused") {
|
|
1596
|
+
// for focused mode update this.focusedElementId to update the rendering, else it wont find the needed html elements
|
|
1597
|
+
// focusedElementId will get validated in the template
|
|
1598
|
+
this.focusedElementId = elementId;
|
|
1599
|
+
await sleep(0);
|
|
1600
|
+
}
|
|
1601
|
+
if (this.renderStyle === "view") {
|
|
1602
|
+
this.expandAndGotoOperation(elementId, expandPath, true);
|
|
1603
|
+
} else {
|
|
1604
|
+
const contentEl = this.getTargetElement(elementId);
|
|
1605
|
+
const isValidElementId = Boolean(contentEl);
|
|
1606
|
+
if (contentEl) {
|
|
1607
|
+
const firstNavEl = this.shadowRoot.querySelector(".first-nav-item");
|
|
1608
|
+
const isFirst =
|
|
1609
|
+
isFirstNavItem || (firstNavEl && firstNavEl.dataset.contentId === elementId);
|
|
1610
|
+
const mainContentEl = this.shadowRoot.querySelector(".main-content");
|
|
1611
|
+
let targetTop: number;
|
|
1612
|
+
|
|
1613
|
+
if (this.autoHeight === "true" || this.hasAttribute("auto-height")) {
|
|
1614
|
+
this.scrollAutoHeightTarget(contentEl, elementId);
|
|
1615
|
+
} else if (isFirst) {
|
|
1616
|
+
targetTop = 0;
|
|
1617
|
+
this.lockObserverDuringScroll(contentEl, elementId, targetTop);
|
|
1618
|
+
if (mainContentEl) {
|
|
1619
|
+
mainContentEl.scrollTo({ top: 0, behavior: this.getContentScrollBehavior() });
|
|
1620
|
+
} else {
|
|
1621
|
+
contentEl.scrollIntoView({ behavior: this.getContentScrollBehavior(), block: "start" });
|
|
1622
|
+
}
|
|
1623
|
+
} else if (mainContentEl) {
|
|
1624
|
+
const containerRect = mainContentEl.getBoundingClientRect();
|
|
1625
|
+
const targetRect = contentEl.getBoundingClientRect();
|
|
1626
|
+
targetTop = targetRect.top - containerRect.top + mainContentEl.scrollTop;
|
|
1627
|
+
this.lockObserverDuringScroll(contentEl, elementId, targetTop);
|
|
1628
|
+
mainContentEl.scrollTo({ top: targetTop, behavior: this.getContentScrollBehavior() });
|
|
1629
|
+
} else {
|
|
1630
|
+
this.lockObserverDuringScroll(contentEl, elementId);
|
|
1631
|
+
contentEl.scrollIntoView({ behavior: this.getContentScrollBehavior(), block: "start" });
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
if (isValidElementId) {
|
|
1635
|
+
// In focused mode, <api-request> and <api-response> component instances are reused across endpoint switches
|
|
1636
|
+
// to avoid expensive DOM re-creation. Reset their selection state so selections from the previous operation
|
|
1637
|
+
// (such as alternative request bodies or non-default status codes) do not persist into the new operation.
|
|
1638
|
+
if (this.renderStyle === "focused") {
|
|
1639
|
+
const requestEl = this.shadowRoot.querySelector("api-request");
|
|
1640
|
+
if (typeof requestEl?.afterNavigationFocusedMode === "function") {
|
|
1641
|
+
requestEl.afterNavigationFocusedMode();
|
|
1642
|
+
}
|
|
1643
|
+
const responseEl = this.shadowRoot.querySelector("api-response");
|
|
1644
|
+
if (typeof responseEl?.resetSelection === "function") {
|
|
1645
|
+
responseEl.resetSelection();
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// Update Location Hash
|
|
1650
|
+
this._activeElementId = contentEl?.id || elementId;
|
|
1651
|
+
this.dispatchEvent(
|
|
1652
|
+
new CustomEvent("nav-active-change", {
|
|
1653
|
+
detail: {
|
|
1654
|
+
id: elementId,
|
|
1655
|
+
elementId,
|
|
1656
|
+
},
|
|
1657
|
+
bubbles: true,
|
|
1658
|
+
composed: true,
|
|
1659
|
+
}),
|
|
1660
|
+
);
|
|
1661
|
+
if (this.updateRoute === "true") {
|
|
1662
|
+
this.replaceHistoryState(elementId);
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
// Update NavBar View and Styles
|
|
1666
|
+
const newNavEl = this.shadowRoot.getElementById(`link-${elementId}`);
|
|
1667
|
+
|
|
1668
|
+
if (newNavEl) {
|
|
1669
|
+
expandParentNavContainers(newNavEl);
|
|
1670
|
+
if (scrollNavItemToView) {
|
|
1671
|
+
const navScrollEl = this.shadowRoot.querySelector(".nav-scroll");
|
|
1672
|
+
if (navScrollEl) {
|
|
1673
|
+
const scrollRect = navScrollEl.getBoundingClientRect();
|
|
1674
|
+
const itemRect = newNavEl.getBoundingClientRect();
|
|
1675
|
+
const targetTop =
|
|
1676
|
+
itemRect.top -
|
|
1677
|
+
scrollRect.top +
|
|
1678
|
+
navScrollEl.scrollTop -
|
|
1679
|
+
navScrollEl.clientHeight / 2;
|
|
1680
|
+
navScrollEl.scrollTo({ top: targetTop, behavior: this.scrollBehavior });
|
|
1681
|
+
} else {
|
|
1682
|
+
newNavEl.scrollIntoView({ behavior: this.scrollBehavior, block: "center" });
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
await sleep(0);
|
|
1686
|
+
const oldNavEl = this.shadowRoot.querySelector(
|
|
1687
|
+
".nav-bar-tag.active, .nav-bar-path.active, .nav-bar-info.active, .nav-bar-h1.active, .nav-bar-h2.active, .operations.active",
|
|
1688
|
+
);
|
|
1689
|
+
if (oldNavEl) {
|
|
1690
|
+
oldNavEl.classList.remove("active");
|
|
1691
|
+
oldNavEl.part.remove("active");
|
|
1692
|
+
oldNavEl.part.remove("section-navbar-active-item");
|
|
1693
|
+
}
|
|
1694
|
+
newNavEl.classList.add("active"); // must add the class after scrolling
|
|
1695
|
+
newNavEl.part.add("section-navbar-active-item");
|
|
1696
|
+
// this.requestUpdate();
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
// Public Method - to update security-scheme of type http
|
|
1703
|
+
setHttpUserNameAndPassword(securitySchemeId, username, password) {
|
|
1704
|
+
return applyApiKey.call(this, securitySchemeId, username, password);
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
// Public Method - to update security-scheme of type apiKey or OAuth.
|
|
1708
|
+
// options.note: where the key came from (e.g. "Using your sign-in"), shown in place of the scheme's form
|
|
1709
|
+
setApiKey(securitySchemeId, apiKeyValue, options: { note?: string } = {}) {
|
|
1710
|
+
return applyApiKey.call(this, securitySchemeId, "", "", apiKeyValue, options?.note || "");
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
// Public Method
|
|
1714
|
+
removeAllSecurityKeys() {
|
|
1715
|
+
return onClearAllApiKeys.call(this);
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
// Public Method
|
|
1719
|
+
setApiServer(apiServerUrl) {
|
|
1720
|
+
// return apiServerUrl;
|
|
1721
|
+
return setApiServer.call(this, apiServerUrl);
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
// Getter for normalized active MCP targets
|
|
1725
|
+
get mcpTargets() {
|
|
1726
|
+
return parseMcpTargets(this.mcp);
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
// Public Method - Generates MCP Server code and configuration in memory
|
|
1730
|
+
generateMcpServer(target, options = {}) {
|
|
1731
|
+
const spec = this.resolvedSpec || this.openApiSpec;
|
|
1732
|
+
if (!spec) return null;
|
|
1733
|
+
const defaultTarget = this.mcpTargets[0] || "typescript";
|
|
1734
|
+
return mcpServerGenerator(spec, {
|
|
1735
|
+
target: target || defaultTarget,
|
|
1736
|
+
serverName: this.mcpServerName,
|
|
1737
|
+
...options,
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
// Public Method - Triggers browser download of generated MCP server file
|
|
1742
|
+
downloadMcpServer(target, options = {}) {
|
|
1743
|
+
const defaultTarget = this.mcpTargets[0] || "typescript";
|
|
1744
|
+
const result = this.generateMcpServer(target || defaultTarget, options);
|
|
1745
|
+
if (!result || !result.serverCode) return;
|
|
1746
|
+
const t = result.target;
|
|
1747
|
+
let filename = "server.ts";
|
|
1748
|
+
let mime = "text/typescript";
|
|
1749
|
+
|
|
1750
|
+
if (t === "python") {
|
|
1751
|
+
filename = "server.py";
|
|
1752
|
+
mime = "text/x-python";
|
|
1753
|
+
} else if (t === "go") {
|
|
1754
|
+
filename = "main.go";
|
|
1755
|
+
mime = "text/x-go";
|
|
1756
|
+
} else if (t === "csharp" || t === "c#") {
|
|
1757
|
+
filename = "Program.cs";
|
|
1758
|
+
mime = "text/plain";
|
|
1759
|
+
} else if (t === "rust") {
|
|
1760
|
+
filename = "main.rs";
|
|
1761
|
+
mime = "text/x-rust";
|
|
1762
|
+
} else if (t === "java") {
|
|
1763
|
+
filename = "RapiDocMcpServer.java";
|
|
1764
|
+
mime = "text/x-java-source";
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
const blob = new Blob([result.serverCode], { type: mime });
|
|
1768
|
+
const link = document.createElement("a");
|
|
1769
|
+
link.href = URL.createObjectURL(blob);
|
|
1770
|
+
link.download = filename;
|
|
1771
|
+
document.body.appendChild(link);
|
|
1772
|
+
link.click();
|
|
1773
|
+
document.body.removeChild(link);
|
|
1774
|
+
URL.revokeObjectURL(link.href);
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
// Public Method - Returns token budget diagnostics for MCP tooling
|
|
1778
|
+
getMcpBudget(options = {}) {
|
|
1779
|
+
const spec = this.resolvedSpec || this.openApiSpec;
|
|
1780
|
+
if (!spec) return null;
|
|
1781
|
+
const extracted = extractMcpOperations(spec, options);
|
|
1782
|
+
return estimateTokenCost(extracted.tools, extracted.resources, extracted.workflows);
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
// Event handler for Advanced Search text-inputs and checkboxes
|
|
1786
|
+
onAdvancedSearch(ev, delay) {
|
|
1787
|
+
const eventTargetEl = ev.target;
|
|
1788
|
+
clearTimeout(this.timeoutId);
|
|
1789
|
+
this.timeoutId = setTimeout(() => {
|
|
1790
|
+
let searchInputEl;
|
|
1791
|
+
if (eventTargetEl.type === "text") {
|
|
1792
|
+
searchInputEl = eventTargetEl;
|
|
1793
|
+
} else {
|
|
1794
|
+
searchInputEl = eventTargetEl
|
|
1795
|
+
.closest(".advanced-search-options")
|
|
1796
|
+
.querySelector("input[type=text]");
|
|
1797
|
+
}
|
|
1798
|
+
const searchOptions = [
|
|
1799
|
+
...eventTargetEl.closest(".advanced-search-options").querySelectorAll("input:checked"),
|
|
1800
|
+
].map((v) => v.id);
|
|
1801
|
+
this.advancedSearchMatches = advancedSearch(
|
|
1802
|
+
searchInputEl.value,
|
|
1803
|
+
this.resolvedSpec.tags,
|
|
1804
|
+
searchOptions,
|
|
1805
|
+
);
|
|
1806
|
+
}, delay);
|
|
1807
|
+
}
|
|
1808
|
+
}
|