openapi-explorer 0.9.310 → 0.9.314
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/CHANGELOG.md +1 -0
- package/dist/{openapi-explorer.min.js → browser/openapi-explorer.min.js} +3 -3
- package/dist/{openapi-explorer.min.js.map → browser/openapi-explorer.min.js.map} +0 -0
- package/dist/es/components/api-request.js +936 -0
- package/dist/es/components/api-response.js +184 -0
- package/dist/es/components/json-tree.js +67 -0
- package/{src → dist/es}/components/mime-types.js +11 -17
- package/dist/es/components/schema-table.js +156 -0
- package/dist/es/components/schema-tree.js +191 -0
- package/dist/es/components/tag-input.js +67 -0
- package/{src → dist/es}/openapi-explorer-oauth-handler.js +2 -2
- package/{src → dist/es}/openapi-explorer.js +364 -371
- package/dist/es/styles/advanced-search-styles.js +2 -0
- package/dist/es/styles/api-request-styles.js +2 -0
- package/dist/es/styles/border-styles.js +2 -0
- package/dist/es/styles/endpoint-styles.js +2 -0
- package/dist/es/styles/flex-styles.js +2 -0
- package/dist/es/styles/font-styles.js +2 -0
- package/dist/es/styles/info-styles.js +2 -0
- package/dist/es/styles/input-styles.js +4 -0
- package/dist/es/styles/nav-styles.js +2 -0
- package/dist/es/styles/prism-styles.js +2 -0
- package/dist/es/styles/schema-styles.js +2 -0
- package/dist/es/styles/tab-styles.js +2 -0
- package/dist/es/styles/table-styles.js +2 -0
- package/dist/es/templates/advance-search-template.js +37 -0
- package/dist/es/templates/callback-template.js +7 -0
- package/dist/es/templates/code-samples-template.js +26 -0
- package/dist/es/templates/components-template.js +17 -0
- package/dist/es/templates/endpoint-template.js +94 -0
- package/dist/es/templates/expanded-endpoint-template.js +32 -0
- package/{src → dist/es}/templates/focused-endpoint-template.js +15 -15
- package/dist/es/templates/navbar-template.js +46 -0
- package/dist/es/templates/overview-template.js +9 -0
- package/dist/es/templates/responsiveViewMainBodyTemplate.js +30 -0
- package/dist/es/templates/security-scheme-template.js +330 -0
- package/dist/es/templates/server-template.js +42 -0
- package/{src → dist/es}/utils/color-utils.js +53 -16
- package/{src → dist/es}/utils/common-utils.js +18 -18
- package/{src → dist/es}/utils/schema-utils.js +248 -124
- package/{src → dist/es}/utils/spec-parser.js +112 -71
- package/dist/es/utils/theme.js +75 -0
- package/{src → dist/es}/utils/xml/xml.js +41 -38
- package/dist/lib/components/api-request.js +957 -0
- package/dist/lib/components/api-response.js +206 -0
- package/dist/lib/components/json-tree.js +82 -0
- package/dist/lib/components/mime-types.js +70 -0
- package/dist/lib/components/schema-table.js +170 -0
- package/dist/lib/components/schema-tree.js +206 -0
- package/dist/lib/components/tag-input.js +76 -0
- package/dist/lib/openapi-explorer-oauth-handler.js +19 -0
- package/dist/lib/openapi-explorer.js +817 -0
- package/dist/lib/styles/advanced-search-styles.js +10 -0
- package/dist/lib/styles/api-request-styles.js +10 -0
- package/dist/lib/styles/border-styles.js +10 -0
- package/dist/lib/styles/endpoint-styles.js +10 -0
- package/dist/lib/styles/flex-styles.js +10 -0
- package/dist/lib/styles/font-styles.js +10 -0
- package/dist/lib/styles/info-styles.js +10 -0
- package/dist/lib/styles/input-styles.js +11 -0
- package/dist/lib/styles/nav-styles.js +10 -0
- package/dist/lib/styles/prism-styles.js +10 -0
- package/dist/lib/styles/schema-styles.js +10 -0
- package/dist/lib/styles/tab-styles.js +10 -0
- package/dist/lib/styles/table-styles.js +10 -0
- package/dist/lib/templates/advance-search-template.js +42 -0
- package/dist/lib/templates/callback-template.js +12 -0
- package/dist/lib/templates/code-samples-template.js +36 -0
- package/dist/lib/templates/components-template.js +27 -0
- package/dist/lib/templates/endpoint-template.js +111 -0
- package/dist/lib/templates/expanded-endpoint-template.js +48 -0
- package/dist/lib/templates/focused-endpoint-template.js +95 -0
- package/dist/lib/templates/navbar-template.js +54 -0
- package/dist/lib/templates/overview-template.js +16 -0
- package/dist/lib/templates/responsiveViewMainBodyTemplate.js +47 -0
- package/dist/lib/templates/security-scheme-template.js +342 -0
- package/dist/lib/templates/server-template.js +49 -0
- package/dist/lib/utils/color-utils.js +112 -0
- package/dist/lib/utils/common-utils.js +156 -0
- package/dist/lib/utils/schema-utils.js +743 -0
- package/dist/lib/utils/spec-parser.js +361 -0
- package/dist/lib/utils/theme.js +84 -0
- package/dist/lib/utils/xml/xml.js +239 -0
- package/package.json +19 -6
- package/dist/openapi-explorer.min.js.LICENSE.txt +0 -71
- package/dist/openapi-explorer.min.js.LICENSE.txt.gz +0 -0
- package/dist/openapi-explorer.min.js.gz +0 -0
- package/dist/openapi-explorer.min.js.map.gz +0 -0
- package/dist/report.html +0 -38
- package/src/components/api-request.js +0 -1244
- package/src/components/api-response.js +0 -340
- package/src/components/json-tree.js +0 -129
- package/src/components/schema-table.js +0 -250
- package/src/components/schema-tree.js +0 -280
- package/src/components/tag-input.js +0 -109
- package/src/styles/advanced-search-styles.js +0 -84
- package/src/styles/api-request-styles.js +0 -111
- package/src/styles/border-styles.js +0 -24
- package/src/styles/css/main.css +0 -24
- package/src/styles/endpoint-styles.js +0 -222
- package/src/styles/flex-styles.js +0 -15
- package/src/styles/font-styles.js +0 -266
- package/src/styles/info-styles.js +0 -20
- package/src/styles/input-styles.js +0 -236
- package/src/styles/nav-styles.js +0 -141
- package/src/styles/prism-styles.js +0 -107
- package/src/styles/schema-styles.js +0 -121
- package/src/styles/tab-styles.js +0 -44
- package/src/styles/table-styles.js +0 -48
- package/src/templates/advance-search-template.js +0 -81
- package/src/templates/callback-template.js +0 -63
- package/src/templates/code-samples-template.js +0 -35
- package/src/templates/components-template.js +0 -43
- package/src/templates/endpoint-template.js +0 -175
- package/src/templates/expanded-endpoint-template.js +0 -104
- package/src/templates/navbar-template.js +0 -175
- package/src/templates/overview-template.js +0 -58
- package/src/templates/responsiveViewMainBodyTemplate.js +0 -72
- package/src/templates/security-scheme-template.js +0 -487
- package/src/templates/server-template.js +0 -106
- package/src/utils/theme.js +0 -163
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { html } from 'lit-element';
|
|
2
|
+
import { unsafeHTML } from 'lit-html/directives/unsafe-html';
|
|
3
|
+
import { marked } from 'marked';
|
|
4
|
+
import base64url from 'base64url';
|
|
5
|
+
|
|
6
|
+
function onApiKeyChange(apiKeyId, e) {
|
|
7
|
+
let apiKeyValue = '';
|
|
8
|
+
const securityObj = this.resolvedSpec.securitySchemes.find(v => v.apiKeyId === apiKeyId);
|
|
9
|
+
|
|
10
|
+
if (!securityObj) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const trEl = e.target.closest('tr');
|
|
15
|
+
|
|
16
|
+
if (securityObj.type && securityObj.type === 'http' && securityObj.scheme && securityObj.scheme.toLowerCase() === 'basic') {
|
|
17
|
+
const userVal = trEl.querySelector('.api-key-user').value.trim();
|
|
18
|
+
const passwordVal = trEl.querySelector('.api-key-password').value.trim();
|
|
19
|
+
|
|
20
|
+
if (passwordVal) {
|
|
21
|
+
apiKeyValue = `Basic ${btoa(`${userVal}:${passwordVal}`)}`;
|
|
22
|
+
}
|
|
23
|
+
} else {
|
|
24
|
+
apiKeyValue = trEl.querySelector('.api-key-input').value.trim();
|
|
25
|
+
|
|
26
|
+
if (apiKeyValue) {
|
|
27
|
+
if (securityObj.scheme && securityObj.scheme.toLowerCase() === 'bearer') {
|
|
28
|
+
apiKeyValue = `Bearer ${apiKeyValue.replace(/^Bearer\s+/i, '')}`;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
securityObj.finalKeyValue = apiKeyValue;
|
|
34
|
+
this.requestUpdate();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function onClearAllApiKeys() {
|
|
38
|
+
this.resolvedSpec.securitySchemes.forEach(v => {
|
|
39
|
+
v.user = '';
|
|
40
|
+
v.password = '';
|
|
41
|
+
v.value = '';
|
|
42
|
+
v.finalKeyValue = '';
|
|
43
|
+
});
|
|
44
|
+
this.requestUpdate();
|
|
45
|
+
} // Updates the OAuth Access Token (API key), so it reflects in UI and gets used in TRY calls
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
function updateOAuthKey(apiKeyId, tokenType = 'Bearer', accessToken) {
|
|
49
|
+
const securityObj = this.resolvedSpec.securitySchemes.find(v => v.apiKeyId === apiKeyId);
|
|
50
|
+
const tokenPrefix = tokenType && tokenType.toLowerCase() === 'bearer' ? 'Bearer' : tokenType;
|
|
51
|
+
securityObj.finalKeyValue = `${tokenPrefix}${tokenPrefix ? ' ' : ''}${accessToken}`;
|
|
52
|
+
this.requestUpdate();
|
|
53
|
+
} // Gets Access-Token in exchange of Authorization Code
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
async function fetchAccessToken(tokenUrl, clientId, clientSecret, redirectUrl, grantType, authCode, sendClientSecretIn = 'header', apiKeyId, authFlowDivEl, scopes = null) {
|
|
57
|
+
const respDisplayEl = authFlowDivEl ? authFlowDivEl.querySelector('.oauth-resp-display') : undefined;
|
|
58
|
+
const urlFormParams = new URLSearchParams();
|
|
59
|
+
const headers = new Headers();
|
|
60
|
+
urlFormParams.append('grant_type', grantType);
|
|
61
|
+
|
|
62
|
+
if (redirectUrl) {
|
|
63
|
+
urlFormParams.append('redirect_uri', redirectUrl);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (authCode) {
|
|
67
|
+
urlFormParams.append('code', authCode);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (sendClientSecretIn === 'header') {
|
|
71
|
+
headers.set('Authorization', `Basic ${btoa(`${clientId}:${clientSecret}`)}`);
|
|
72
|
+
} else {
|
|
73
|
+
urlFormParams.append('client_id', clientId);
|
|
74
|
+
|
|
75
|
+
if (clientSecret) {
|
|
76
|
+
urlFormParams.append('client_secret', clientSecret);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (scopes) {
|
|
81
|
+
urlFormParams.append('scope', scopes);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const {
|
|
85
|
+
codeVerifier
|
|
86
|
+
} = JSON.parse(localStorage.getItem('openapi-explorer-oauth') || '{}');
|
|
87
|
+
localStorage.removeItem('openapi-explorer-oauth');
|
|
88
|
+
|
|
89
|
+
if (codeVerifier) {
|
|
90
|
+
urlFormParams.append('code_verifier', codeVerifier);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const resp = await fetch(tokenUrl, {
|
|
95
|
+
method: 'POST',
|
|
96
|
+
headers,
|
|
97
|
+
body: urlFormParams
|
|
98
|
+
});
|
|
99
|
+
const tokenResp = await resp.json();
|
|
100
|
+
|
|
101
|
+
if (!resp.ok) {
|
|
102
|
+
if (respDisplayEl) {
|
|
103
|
+
respDisplayEl.innerHTML = `<span style="color:var(--red)">${tokenResp.error_description || tokenResp.error_description || 'Unable to get access token'}</span>`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (tokenResp.token_type && tokenResp.access_token) {
|
|
110
|
+
updateOAuthKey.call(this, apiKeyId, tokenResp.token_type, tokenResp.access_token);
|
|
111
|
+
|
|
112
|
+
if (respDisplayEl) {
|
|
113
|
+
respDisplayEl.innerHTML = '<span style="color:var(--green)">Access Token Received</span>';
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} catch (err) {
|
|
117
|
+
if (respDisplayEl) {
|
|
118
|
+
respDisplayEl.innerHTML = '<span style="color:var(--red)">Failed to get access token</span>';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getCookieValue(keyId) {
|
|
124
|
+
const foundCookie = (document.cookie || '').split(';').find(c => c.split('=')[0] === keyId);
|
|
125
|
+
return foundCookie && foundCookie.split('=')[1] || '';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function toObject(urlSearchParams) {
|
|
129
|
+
const result = {};
|
|
130
|
+
const entries = urlSearchParams && urlSearchParams.entries() || [];
|
|
131
|
+
|
|
132
|
+
for (const [key, value] of entries) {
|
|
133
|
+
result[key] = value;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return result;
|
|
137
|
+
} // Gets invoked when it receives the Authorization Code from the other window via message-event
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
export async function checkForAuthToken(redirectToApiLocation) {
|
|
141
|
+
const parameters = toObject(new URLSearchParams(window.location.search));
|
|
142
|
+
const hashQuery = toObject(new URLSearchParams(window.location.hash.slice(1)));
|
|
143
|
+
Object.assign(parameters, hashQuery);
|
|
144
|
+
const newUrl = new URL(window.location);
|
|
145
|
+
newUrl.searchParams.delete('nonce');
|
|
146
|
+
newUrl.searchParams.delete('expires_in');
|
|
147
|
+
newUrl.searchParams.delete('access_token');
|
|
148
|
+
newUrl.searchParams.delete('token_type');
|
|
149
|
+
newUrl.searchParams.delete('id_token');
|
|
150
|
+
newUrl.searchParams.delete('state');
|
|
151
|
+
newUrl.searchParams.delete('code');
|
|
152
|
+
newUrl.searchParams.delete('iss');
|
|
153
|
+
newUrl.searchParams.delete('scope');
|
|
154
|
+
newUrl.searchParams.delete('prompt');
|
|
155
|
+
newUrl.searchParams.delete('hd');
|
|
156
|
+
newUrl.searchParams.delete('authuser');
|
|
157
|
+
newUrl.searchParams.delete('redirect_auth');
|
|
158
|
+
|
|
159
|
+
if (!parameters.state) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const sanitizedUrlWithHash = newUrl.toString().replace(/#((code|state|access_token|id_token|authuser|expires_in|hd|prompt|scope|token_type)=[^&]+&?)*$/ig, '');
|
|
164
|
+
history.replaceState({}, undefined, sanitizedUrlWithHash);
|
|
165
|
+
const {
|
|
166
|
+
apiKeyId,
|
|
167
|
+
flowId,
|
|
168
|
+
url
|
|
169
|
+
} = JSON.parse(base64url.decode(parameters.state));
|
|
170
|
+
|
|
171
|
+
if (redirectToApiLocation && url && !parameters.redirect_auth) {
|
|
172
|
+
const apiExplorerLocation = new URL(url);
|
|
173
|
+
Object.keys(parameters).forEach(key => apiExplorerLocation.searchParams.append(key, parameters[key]));
|
|
174
|
+
apiExplorerLocation.searchParams.append('redirect_auth', true);
|
|
175
|
+
window.location.replace(apiExplorerLocation.toString());
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (parameters.code) {
|
|
180
|
+
const securityObj = this.resolvedSpec.securitySchemes.find(v => v.apiKeyId === apiKeyId);
|
|
181
|
+
const tokenUrl = securityObj && securityObj.flows[flowId] && new URL(securityObj.flows[flowId].tokenUrl || '', this.selectedServer.computedUrl);
|
|
182
|
+
await fetchAccessToken.call(this, tokenUrl, securityObj.clientId, securityObj.clientSecret, securityObj.redirectUri || window.location.href, 'authorization_code', parameters.code, null, apiKeyId);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
updateOAuthKey.call(this, apiKeyId, parameters.token_type, parameters.access_token);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function onInvokeOAuthFlow(apiKeyId, flowType, authUrl, tokenUrl, e) {
|
|
190
|
+
const authFlowDivEl = e.target.closest('.oauth-flow');
|
|
191
|
+
const clientId = authFlowDivEl.querySelector('.oauth-client-id') ? authFlowDivEl.querySelector('.oauth-client-id').value.trim() : '';
|
|
192
|
+
const clientSecret = authFlowDivEl.querySelector('.oauth-client-secret') ? authFlowDivEl.querySelector('.oauth-client-secret').value.trim() : '';
|
|
193
|
+
const sendClientSecretIn = authFlowDivEl.querySelector('.oauth-send-client-secret-in') ? authFlowDivEl.querySelector('.oauth-send-client-secret-in').value.trim() : 'header';
|
|
194
|
+
const checkedScopeEls = [...authFlowDivEl.querySelectorAll('input[type="checkbox"]:checked')];
|
|
195
|
+
const securityObj = this.resolvedSpec.securitySchemes.find(v => v.apiKeyId === apiKeyId);
|
|
196
|
+
let grantType = '';
|
|
197
|
+
let responseType = ''; // clear previous error messages
|
|
198
|
+
|
|
199
|
+
const errEls = [...authFlowDivEl.parentNode.querySelectorAll('.oauth-resp-display')];
|
|
200
|
+
errEls.forEach(v => {
|
|
201
|
+
v.innerHTML = '';
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
if (flowType === 'authorizationCode' || flowType === 'implicit') {
|
|
205
|
+
const authUrlObj = new URL(authUrl);
|
|
206
|
+
const authCodeParams = new URLSearchParams(authUrlObj.search);
|
|
207
|
+
|
|
208
|
+
if (flowType === 'authorizationCode') {
|
|
209
|
+
const randomBytes = new Uint32Array(3);
|
|
210
|
+
(window.crypto || window.msCrypto).getRandomValues(randomBytes);
|
|
211
|
+
authCodeParams.set('nonce', randomBytes.toString('hex').split(',').join(''));
|
|
212
|
+
grantType = 'authorization_code';
|
|
213
|
+
responseType = 'code';
|
|
214
|
+
const codeVerifier = randomBytes.toString('hex').split(',').join('');
|
|
215
|
+
const hash = await (window.crypto || window.msCrypto).subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier));
|
|
216
|
+
const codeChallenge = base64url(hash);
|
|
217
|
+
authCodeParams.set('code_challenge', codeChallenge);
|
|
218
|
+
authCodeParams.set('code_challenge_method', 'S256');
|
|
219
|
+
localStorage.setItem('openapi-explorer-oauth', JSON.stringify({
|
|
220
|
+
codeVerifier
|
|
221
|
+
}));
|
|
222
|
+
} else if (flowType === 'implicit') {
|
|
223
|
+
responseType = 'token';
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const selectedScopes = checkedScopeEls.map(v => v.value).join(' ');
|
|
227
|
+
|
|
228
|
+
if (selectedScopes) {
|
|
229
|
+
authCodeParams.set('scope', selectedScopes);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
authCodeParams.set('client_id', clientId);
|
|
233
|
+
authCodeParams.set('redirect_uri', securityObj.redirectUri || window.location.href);
|
|
234
|
+
authCodeParams.set('response_type', responseType);
|
|
235
|
+
authCodeParams.set('state', base64url.encode(JSON.stringify({
|
|
236
|
+
apiKeyId,
|
|
237
|
+
flowId: flowType,
|
|
238
|
+
url: window.location.href
|
|
239
|
+
})));
|
|
240
|
+
authUrlObj.search = authCodeParams.toString();
|
|
241
|
+
window.location.assign(authUrlObj.toString());
|
|
242
|
+
} else if (flowType === 'clientCredentials') {
|
|
243
|
+
grantType = 'client_credentials';
|
|
244
|
+
const selectedScopes = checkedScopeEls.map(v => v.value).join(' ');
|
|
245
|
+
fetchAccessToken.call(this, tokenUrl, clientId, clientSecret, '', grantType, '', sendClientSecretIn, apiKeyId, authFlowDivEl, selectedScopes);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/* eslint-disable indent */
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
function oAuthFlowTemplate(flowName, securityObj, authFlow) {
|
|
252
|
+
const apiKeyId = securityObj.apiKeyId;
|
|
253
|
+
|
|
254
|
+
const getFullUrl = url => url ? new URL(url, this.selectedServer.computedUrl) : undefined;
|
|
255
|
+
|
|
256
|
+
const authorizationUrl = getFullUrl(authFlow.authorizationUrl, this.selectedServer.computedUrl);
|
|
257
|
+
const tokenUrl = getFullUrl(authFlow.tokenUrl, this.selectedServer.computedUrl);
|
|
258
|
+
const refreshUrl = getFullUrl(authFlow.refreshUrl, this.selectedServer.computedUrl);
|
|
259
|
+
let flowNameDisplay;
|
|
260
|
+
|
|
261
|
+
if (flowName === 'authorizationCode') {
|
|
262
|
+
flowNameDisplay = 'Authorization Code Flow';
|
|
263
|
+
} else if (flowName === 'clientCredentials') {
|
|
264
|
+
flowNameDisplay = 'Client Credentials Flow';
|
|
265
|
+
} else if (flowName === 'implicit') {
|
|
266
|
+
flowNameDisplay = 'Implicit Flow';
|
|
267
|
+
} else {
|
|
268
|
+
flowNameDisplay = flowName;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return html` <div class="oauth-flow" style="padding:10px 0;margin-bottom:10px"> <div class="tiny-title upper" style="margin-bottom:5px">${flowNameDisplay}</div> ${authorizationUrl ? html`<div><span style="width:75px;display:inline-block">Auth URL</span> <span class="mono-font"> ${authorizationUrl} </span></div>` : ''} ${tokenUrl ? html`<div><span style="width:75px;display:inline-block">Token URL</span> <span class="mono-font">${tokenUrl}</span></div>` : ''} ${refreshUrl ? html`<div><span style="width:75px;display:inline-block">Refresh URL</span> <span class="mono-font">${refreshUrl}</span></div>` : ''} ${flowName === 'authorizationCode' || flowName === 'clientCredentials' || flowName === 'implicit' ? html` ${authFlow.scopes ? html` <span> Scopes </span> <div class="oauth-scopes" part="section-auth-scopes" style="width:100%;display:flex;flex-direction:column;flex-wrap:wrap;margin:0 0 .125rem 0"> ${Object.entries(authFlow.scopes).map((scopeAndDescr, index) => html` <div class="m-checkbox" style="display:inline-flex;align-items:center"> <input type="checkbox" checked="checked" part="checkbox checkbox-auth-scope" id="${flowName}${index}" value="${scopeAndDescr[0]}"> <label for="${flowName}${index}" style="margin-left:5px"> <span class="mono-font">${scopeAndDescr[0]}</span> ${scopeAndDescr[0] !== scopeAndDescr[1] ? ` - ${scopeAndDescr[1] || ''}` : ''} </label> </div> `)} </div> ` : ''} <div style="display:flex"> <input type="text" part="textbox textbox-auth-client-id" value="${securityObj.clientId || ''}" placeholder="Client ID" spellcheck="false" class="oauth-client-id"> ${flowName === 'clientCredentials' ? html` <input type="password" part="textbox textbox-auth-client-secret" value="" placeholder="Client Secret" spellcheck="false" class="oauth-client-secret" style="margin:0 5px"> <select aria-label="oauth client secret location" style="margin-right:5px" class="oauth-send-client-secret-in"> <option value="header" selected="selected"> Authorization Header </option> <option value="request-body"> Request Body </option> </select>` : html`<div style="width:5px"></div>`} ${flowName === 'authorizationCode' || flowName === 'clientCredentials' || flowName === 'implicit' ? html` <button class="m-btn thin-border" part="btn btn-outline" @click="${e => {
|
|
272
|
+
onInvokeOAuthFlow.call(this, apiKeyId, flowName, authorizationUrl, tokenUrl, e);
|
|
273
|
+
}}">GET TOKEN</button>` : ''} </div> <div class="oauth-resp-display red-text small-font-size"></div> ` : ''} </div> `;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export default function securitySchemeTemplate() {
|
|
277
|
+
const schemes = this.resolvedSpec && this.resolvedSpec.securitySchemes;
|
|
278
|
+
|
|
279
|
+
if (!schemes) {
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const providedApiKeys = schemes.filter(v => v.finalKeyValue);
|
|
284
|
+
return html` <section id="auth" part="section-auth" class="observe-me ${this.renderStyle === 'focused' ? 'section-gap--focused-mode' : 'section-gap'}"> <slot name="authentication"> <div class="section-padding"> <div class="sub-title regular-font">AUTHENTICATION</div> <div class="small-font-size" style="display:flex;align-items:center;min-height:30px"> ${providedApiKeys.length > 0 ? html` <div class="blue-text"> ${providedApiKeys.length} API key applied </div> <div style="flex:1"></div> <button class="m-btn thin-border" part="btn btn-outline" @click="${() => {
|
|
285
|
+
onClearAllApiKeys.call(this);
|
|
286
|
+
}}">CLEAR ALL API KEYS</button>` : html`<div class="red-text">No API key applied</div>`} </div> ${schemes.length > 0 ? html` <table role="presentation" class="m-table" style="width:100%"> ${schemes.map(v => html` <tr> <td style="max-width:500px;overflow-wrap:break-word"> <div style="min-height:24px"> <span style="font-weight:700">${v.typeDisplay}</span> ${v.finalKeyValue ? html` <span class="blue-text"> ${v.finalKeyValue ? 'Key Applied' : ''} </span> <button class="m-btn thin-border small" part="btn btn-outline" @click="${() => {
|
|
287
|
+
v.finalKeyValue = '';
|
|
288
|
+
this.requestUpdate();
|
|
289
|
+
}}">REMOVE</button> ` : ''} </div> ${v.description ? html` <div class="m-markdown"> ${unsafeHTML(marked(v.description || ''))} </div>` : ''} </td> <td> ${v.type && (v.type.toLowerCase() === 'apikey' || v.type.toLowerCase() === 'http' && v.scheme && v.scheme.toLowerCase() === 'bearer') ? html` ${v.type.toLowerCase() === 'apikey' ? html`Send <code>${v.name}</code> in <code>${v.in}</code> with the given value:` : html`Send <code>Authorization</code> in <code>header</code> containing the word <code>Bearer</code> followed by a space and a Token String.`} <div style="display:flex"> ${v.in === 'cookie' ? html` <div style="display:block"> <input type="text" value="${getCookieValue(v.apiKeyId)}" disabled="disabled" class="api-key-input" placeholder="IygRVGf54B59e0GAkKmigGfuiVlp/uhFfk2ifA+jMMJzau2F1jPldc09gPTfnMw13BFBxqUZIFDm55DPfwkb0A==" spellcheck="false" style="resize:horizontal;width:100%"> <br> <small> <strong>Cookies</strong> are set and configured by the remote service, therefore it is not possible to configure them from the browser. </small> </div>` : html` <input type="text" value="${v.value}" class="api-key-input" placeholder="api-token" spellcheck="false"> <button class="m-btn thin-border" style="margin-left:5px" part="btn btn-outline" @click="${e => {
|
|
290
|
+
onApiKeyChange.call(this, v.apiKeyId, e);
|
|
291
|
+
}}"> ${v.finalKeyValue ? 'UPDATE' : 'SET'} </button>`} </div>` : ''} ${v.type && v.type.toLowerCase() === 'http' && v.scheme && v.scheme.toLowerCase() === 'basic' ? html` Send the <code>Authorization</code> header containing the type <code>Basic</code> followed by a space and a base64 encoded string of <code>username:password</code>. <div style="display:flex"> <input type="text" value="${v.user}" placeholder="username" spellcheck="false" class="api-key-user" style="width:100px"> <input type="password" value="${v.password}" placeholder="password" spellcheck="false" class="api-key-password" style="width:100px;margin:0 5px"> <button class="m-btn thin-border" @click="${e => {
|
|
292
|
+
onApiKeyChange.call(this, v.apiKeyId, e);
|
|
293
|
+
}}" part="btn btn-outline"> ${v.finalKeyValue ? 'UPDATE' : 'SET'} </button> </div>` : ''} </td> </tr> ${v.type.toLowerCase() === 'oauth2' ? html` <tr> <td colspan="2" style="border:none;padding-left:48px"> ${Object.keys(v.flows).map(f => oAuthFlowTemplate.call(this, f, v, v.flows[f]))} </td> </tr> ` : ''} `)} </table>` : ''} </div> </slot> </section> `;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function getOauthScopeTemplate(scopes) {
|
|
297
|
+
if (!scopes || !scopes.length || !Array.isArray(scopes)) {
|
|
298
|
+
return '';
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return html` <div> <b>Required scopes:</b> <br> <div style="margin-left:8px"> ${scopes.map(scope => html`<span>${scope}</span> `)} </div> </div>`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function pathSecurityTemplate(pathSecurity) {
|
|
305
|
+
if (this.resolvedSpec.securitySchemes && pathSecurity) {
|
|
306
|
+
const orSecurityKeys1 = [];
|
|
307
|
+
pathSecurity.forEach(pSecurity => {
|
|
308
|
+
const andSecurityKeys1 = [];
|
|
309
|
+
const andKeyTypes = [];
|
|
310
|
+
Object.keys(pSecurity).forEach(pathSecurityKey => {
|
|
311
|
+
const s = this.resolvedSpec.securitySchemes.find(ss => ss.apiKeyId === pathSecurityKey);
|
|
312
|
+
|
|
313
|
+
if (s) {
|
|
314
|
+
andKeyTypes.push(s.typeDisplay);
|
|
315
|
+
andSecurityKeys1.push({ ...s,
|
|
316
|
+
scopes: pSecurity[pathSecurityKey]
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
orSecurityKeys1.push({
|
|
321
|
+
securityTypes: andKeyTypes.length > 1 ? `${andKeyTypes[0]} + ${andKeyTypes.length - 1} more` : andKeyTypes[0],
|
|
322
|
+
securityDefs: andSecurityKeys1
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
return html`<div class="security-info-button" data-content-id="auth" @click="${e => this.scrollToEventTarget(e, false)}"> <div style="position:relative;display:flex;min-width:350px;max-width:700px;justify-content:flex-end"> <svg width="16" height="24" style="cursor:pointer"> <g> <path style="fill:var(--fg3)" d="m13.8,8.5l0,-2.6l0,0c0,-3.2 -2.6,-5.8 -5.8,-5.8s-5.8,2.6 -5.8,5.8l0,0l0,2.6l-2.1,0l0,11.2l16,0l0,-11.2l-2.1,0l-0,0l0,0l0,0l-0,0zm-9.8,-2.6c0,0 0,0 0,0c0,-2.2 1.8,-4 4,-4c2.2,0 4,1.8 4,4c0,0 0,0 0,0l0,2.6l-8.03,0l0,-2.6l0,0l0,0z"/> </g> </svg> ${orSecurityKeys1.map((orSecurityItem1, i) => html` ${i !== 0 ? html`<div style="padding:3px 4px"> OR </div>` : ''} <div class="tooltip" style="cursor:pointer"> <div style="padding:2px 4px;white-space:nowrap;text-overflow:ellipsis;max-width:150px;overflow:hidden"> <span part="anchor anchor-operation-security"> ${orSecurityItem1.securityTypes} </span> </div> <div class="tooltip-text" style="position:absolute;color:var(--fg);top:26px;right:0;border:1px solid var(--border-color);padding:2px 4px;display:block"> ${orSecurityItem1.securityDefs.length > 1 ? html`<div>Requires <b>all</b> of the following </div>` : ''} <div style="padding-left:8px"> ${orSecurityItem1.securityDefs.map((andSecurityItem, j) => html` ${andSecurityItem.type === 'oauth2' ? html` <div> ${orSecurityItem1.securityDefs.length > 1 ? html`<b>${j + 1}.</b> ` : html`Requires`} OAuth token (${andSecurityItem.apiKeyId}) in <b>Authorization header</b> ${getOauthScopeTemplate(andSecurityItem.scopes)} </div>` : andSecurityItem.type === 'http' ? html` <div> ${orSecurityItem1.securityDefs.length > 1 ? html`<b>${j + 1}.</b> ` : html`Requires`} ${andSecurityItem.scheme === 'basic' ? 'Base 64 encoded username:password' : 'Bearer Token'} in <b>Authorization header</b> ${getOauthScopeTemplate(andSecurityItem.scopes)} </div>` : html` <div> ${orSecurityItem1.securityDefs.length > 1 ? html`<b>${j + 1}.</b> ` : html`Requires`} Token in <b>${andSecurityItem.name} ${andSecurityItem.in}</b> ${getOauthScopeTemplate(andSecurityItem.scopes)} </div>`} `)} </div> </div> </div> `)} </div> </div>`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return '';
|
|
329
|
+
}
|
|
330
|
+
/* eslint-enable indent */
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { html } from 'lit-element';
|
|
2
|
+
import { marked } from 'marked';
|
|
3
|
+
import { unsafeHTML } from 'lit-html/directives/unsafe-html';
|
|
4
|
+
|
|
5
|
+
function onApiServerChange(e, server) {
|
|
6
|
+
if (e && e.target.checked) {
|
|
7
|
+
this.selectedServer = server;
|
|
8
|
+
this.requestUpdate();
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function onApiServerVarChange(e, serverObj) {
|
|
13
|
+
const inputEls = [...e.currentTarget.closest('table').querySelectorAll('input, select')];
|
|
14
|
+
let tempUrl = serverObj.url;
|
|
15
|
+
inputEls.forEach(v => {
|
|
16
|
+
const regex = new RegExp(`{${v.dataset.var}}`, 'g');
|
|
17
|
+
tempUrl = tempUrl.replace(regex, v.value);
|
|
18
|
+
});
|
|
19
|
+
serverObj.computedUrl = tempUrl;
|
|
20
|
+
this.requestUpdate();
|
|
21
|
+
}
|
|
22
|
+
/* eslint-disable indent */
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
function serverVarsTemplate() {
|
|
26
|
+
return this.selectedServer && this.selectedServer.variables ? html` <div class="table-title"> SERVER VARIABLES</div> <table role="presentation" class="m-table"> ${Object.entries(this.selectedServer.variables).map(kv => html` <tr> <td style="vertical-align:middle">${kv[0]}</td> <td> ${kv[1].enum ? html` <select data-var="${kv[0]}" @input="${e => {
|
|
27
|
+
onApiServerVarChange.call(this, e, this.selectedServer);
|
|
28
|
+
}}"> ${Object.entries(kv[1].enum).map(e => kv[1].default === e[1] ? html` <option selected="selected" label="${e[1]}" value="${e[1]}">` : html` <option label="${e[1]}" value="${e[1]}">`)} </select>` : html` <input type="text" part="textbox textbox-server-var" spellcheck="false" data-var="${kv[0]}" value="${kv[1].default}" @input="${e => {
|
|
29
|
+
onApiServerVarChange.call(this, e, this.selectedServer);
|
|
30
|
+
}}">`} </td> </tr> ${kv[1].description ? html`<tr><td colspan="2" style="border:none"><span class="m-markdown-small"> ${unsafeHTML(marked(kv[1].description))} </span></td></tr>` : ''} `)} </table> ` : '';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default function serverTemplate() {
|
|
34
|
+
if (!this.resolvedSpec) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return html` <section id="servers" part="section-servers" style="margin-top:24px;margin-bottom:24px" class="regular-font observe-me section-padding ${this.renderStyle === 'read' ? 'section-gap--read-mode' : this.renderStyle === 'focused' ? 'section-gap--focused-mode' : 'section-gap'}"> <div class="sub-title">API SERVER</div> <div class="mono-font" style="margin:12px 0;font-size:calc(var(--font-size-small) + 1px)"> ${!this.resolvedSpec.servers || !this.resolvedSpec.servers.length ? '' : html` ${this.resolvedSpec.servers.map((server, i) => html` <input type="radio" name="api_server" id="srvr-opt-${i}" value="${server.url}" @change="${e => {
|
|
39
|
+
onApiServerChange.call(this, e, server);
|
|
40
|
+
}}" .checked="${this.selectedServer.url === server.url}" style="margin:4px 0;cursor:pointer"> <label style="cursor:pointer" for="srvr-opt-${i}"> ${server.url} ${server.description ? html`- <span class="regular-font">${server.description} </span>` : ''} </label> <br> `)} `} <div class="table-title primary-text" part="label-selected-server"> SELECTED: ${this.selectedServer && this.selectedServer.computedUrl || 'none'}</div> </div> <slot name="servers"></slot> ${serverVarsTemplate.call(this)} </section>`;
|
|
41
|
+
}
|
|
42
|
+
/* eslint-enable indent */
|
|
@@ -1,69 +1,106 @@
|
|
|
1
1
|
/* eslint-disable no-mixed-operators */
|
|
2
|
+
|
|
2
3
|
/* eslint-disable no-bitwise */
|
|
3
4
|
export default {
|
|
4
5
|
color: {
|
|
5
6
|
inputReverseFg: '#fff',
|
|
6
7
|
inputReverseBg: '#333',
|
|
7
8
|
headerBg: '#444',
|
|
9
|
+
|
|
8
10
|
getRgb(hexStr) {
|
|
9
11
|
let hex = (hexStr || '').trim();
|
|
12
|
+
|
|
10
13
|
if (hex.indexOf('#') === 0) {
|
|
11
14
|
hex = hex.slice(1, 7);
|
|
12
|
-
}
|
|
13
|
-
|
|
15
|
+
} // convert 3-digit hex to 6-digits.
|
|
16
|
+
|
|
17
|
+
|
|
14
18
|
if (hex.length === 3 || hex.length === 4) {
|
|
15
19
|
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
16
20
|
}
|
|
21
|
+
|
|
17
22
|
if (hex.length !== 6) {
|
|
18
23
|
// eslint-disable-next-line no-console
|
|
19
24
|
console.error(`Invalid HEX color: '${hexStr}'`);
|
|
20
|
-
return {
|
|
25
|
+
return {
|
|
26
|
+
r: 0,
|
|
27
|
+
g: 0,
|
|
28
|
+
b: 0
|
|
29
|
+
};
|
|
21
30
|
}
|
|
31
|
+
|
|
22
32
|
return {
|
|
23
33
|
r: parseInt(hex.slice(0, 2), 16),
|
|
24
34
|
g: parseInt(hex.slice(2, 4), 16),
|
|
25
|
-
b: parseInt(hex.slice(4, 6), 16)
|
|
35
|
+
b: parseInt(hex.slice(4, 6), 16)
|
|
26
36
|
};
|
|
27
37
|
},
|
|
38
|
+
|
|
28
39
|
luminanace(hexColorCode) {
|
|
29
40
|
const rgb = this.getRgb(hexColorCode);
|
|
30
|
-
return
|
|
41
|
+
return rgb.r * 0.299 + rgb.g * 0.587 + rgb.b * 0.114;
|
|
31
42
|
},
|
|
43
|
+
|
|
32
44
|
invert(hexColorCode) {
|
|
33
45
|
// compare with `>=128`, but giving little more preference to white over black
|
|
34
46
|
return this.luminanace(hexColorCode) > 149 ? '#000000' : '#ffffff';
|
|
35
47
|
},
|
|
48
|
+
|
|
36
49
|
// https://stackoverflow.com/a/41491220/5091874
|
|
37
50
|
selectTextColorFromBackground(bcHexColor) {
|
|
38
|
-
const {
|
|
51
|
+
const {
|
|
52
|
+
r,
|
|
53
|
+
g,
|
|
54
|
+
b
|
|
55
|
+
} = this.getRgb(bcHexColor);
|
|
39
56
|
const colors = [r / 255, g / 255, b / 255];
|
|
40
|
-
const c = colors.map(
|
|
57
|
+
const c = colors.map(col => {
|
|
41
58
|
if (col <= 0.03928) {
|
|
42
59
|
return col / 12.92;
|
|
43
60
|
}
|
|
61
|
+
|
|
44
62
|
return ((col + 0.055) / 1.055) ** 2.4;
|
|
45
63
|
});
|
|
46
|
-
const L =
|
|
47
|
-
return
|
|
64
|
+
const L = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
|
|
65
|
+
return L > 0.179 ? '#000000' : '#FFFFFF';
|
|
48
66
|
},
|
|
67
|
+
|
|
49
68
|
opacity(hex, opacity) {
|
|
50
69
|
const rgb = this.getRgb(hex);
|
|
51
70
|
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${opacity})`;
|
|
52
71
|
},
|
|
72
|
+
|
|
53
73
|
brightness(hex, amt) {
|
|
54
74
|
const rgb = this.getRgb(hex);
|
|
55
75
|
rgb.r += amt;
|
|
56
76
|
rgb.g += amt;
|
|
57
77
|
rgb.b += amt;
|
|
58
|
-
if (rgb.r > 255) {rgb.r = 255;} else if (rgb.r < 0) {rgb.r = 0;}
|
|
59
78
|
|
|
60
|
-
if (rgb.
|
|
79
|
+
if (rgb.r > 255) {
|
|
80
|
+
rgb.r = 255;
|
|
81
|
+
} else if (rgb.r < 0) {
|
|
82
|
+
rgb.r = 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (rgb.g > 255) {
|
|
86
|
+
rgb.g = 255;
|
|
87
|
+
} else if (rgb.g < 0) {
|
|
88
|
+
rgb.g = 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (rgb.b > 255) {
|
|
92
|
+
rgb.b = 255;
|
|
93
|
+
} else if (rgb.b < 0) {
|
|
94
|
+
rgb.b = 0;
|
|
95
|
+
}
|
|
61
96
|
|
|
62
|
-
if (rgb.b > 255) {rgb.b = 255;} else if (rgb.b < 0) {rgb.b = 0;}
|
|
63
97
|
return `#${rgb.r.toString(16).padStart(2, '0')}${rgb.g.toString(16).padStart(2, '0')}${rgb.b.toString(16).padStart(2, '0')}`;
|
|
64
|
-
}
|
|
98
|
+
}
|
|
99
|
+
|
|
65
100
|
},
|
|
101
|
+
|
|
66
102
|
isValidHexColor(colorCode) {
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
|
|
103
|
+
return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|[A-Fa-f0-9]{8}|[A-Fa-f0-9]{4})$/i.test(colorCode);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
};
|
|
@@ -9,21 +9,21 @@ export function debounce(fn, delay) {
|
|
|
9
9
|
}, delay);
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
|
-
|
|
13
12
|
export const invalidCharsRegEx = new RegExp(/[\s#:?&={}]/, 'g'); // used for generating valid html element ids by replacing the invalid chars with hyphen (-)
|
|
14
13
|
|
|
15
14
|
export function sleep(ms) {
|
|
16
|
-
return new Promise(
|
|
15
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
17
16
|
}
|
|
18
|
-
|
|
19
17
|
export function copyToClipboard(data, e) {
|
|
20
18
|
const btnEl = e.currentTarget;
|
|
21
19
|
const textArea = document.createElement('textarea');
|
|
22
20
|
textArea.value = data;
|
|
23
21
|
textArea.style.position = 'fixed'; // avoid scrolling to bottom
|
|
22
|
+
|
|
24
23
|
document.body.appendChild(textArea);
|
|
25
24
|
textArea.focus();
|
|
26
25
|
textArea.select();
|
|
26
|
+
|
|
27
27
|
try {
|
|
28
28
|
document.execCommand('copy');
|
|
29
29
|
btnEl.innerText = 'Copied';
|
|
@@ -33,29 +33,28 @@ export function copyToClipboard(data, e) {
|
|
|
33
33
|
} catch (err) {
|
|
34
34
|
console.error('Unable to copy', err); // eslint-disable-line no-console
|
|
35
35
|
}
|
|
36
|
+
|
|
36
37
|
document.body.removeChild(textArea);
|
|
37
38
|
}
|
|
38
|
-
|
|
39
39
|
export function getBaseUrlFromUrl(url) {
|
|
40
40
|
const pathArray = url.split('/');
|
|
41
41
|
return `${pathArray[0]}//${pathArray[2]}`;
|
|
42
42
|
}
|
|
43
|
-
|
|
44
43
|
export function componentIsInSearch(searchVal, component) {
|
|
45
44
|
return component.name.toLowerCase().includes(searchVal.toLowerCase());
|
|
46
45
|
}
|
|
47
|
-
|
|
48
46
|
export function pathIsInSearch(searchVal, path) {
|
|
49
47
|
const stringToSearch = `${path.method} ${path.path} ${path.summary || path.description || ''} ${path.operationId || ''}`.toLowerCase();
|
|
50
48
|
return stringToSearch.includes(searchVal.toLowerCase());
|
|
51
49
|
}
|
|
52
|
-
|
|
53
50
|
export function schemaKeys(schemaProps, result = new Set()) {
|
|
54
51
|
if (!schemaProps) {
|
|
55
52
|
return result;
|
|
56
53
|
}
|
|
57
|
-
|
|
54
|
+
|
|
55
|
+
Object.keys(schemaProps).forEach(key => {
|
|
58
56
|
result.add(key);
|
|
57
|
+
|
|
59
58
|
if (schemaProps[key].properties) {
|
|
60
59
|
schemaKeys(schemaProps[key].properties, result);
|
|
61
60
|
} else if (schemaProps[key].items && schemaProps[key].items.properties) {
|
|
@@ -64,38 +63,42 @@ export function schemaKeys(schemaProps, result = new Set()) {
|
|
|
64
63
|
});
|
|
65
64
|
return result;
|
|
66
65
|
}
|
|
67
|
-
|
|
68
66
|
export function advancedSearch(searchVal, allSpecTags, searchOptions = []) {
|
|
69
67
|
if (!searchVal.trim() || searchOptions.length === 0) {
|
|
70
68
|
return undefined;
|
|
71
69
|
}
|
|
72
70
|
|
|
73
71
|
const pathsMatched = [];
|
|
74
|
-
allSpecTags.forEach(
|
|
75
|
-
tag.paths.forEach(
|
|
72
|
+
allSpecTags.forEach(tag => {
|
|
73
|
+
tag.paths.forEach(path => {
|
|
76
74
|
let stringToSearch = '';
|
|
75
|
+
|
|
77
76
|
if (searchOptions.includes('search-api-path')) {
|
|
78
77
|
stringToSearch = path.path;
|
|
79
78
|
}
|
|
79
|
+
|
|
80
80
|
if (searchOptions.includes('search-api-descr')) {
|
|
81
81
|
stringToSearch = `${stringToSearch} ${path.summary || path.description || ''}`;
|
|
82
82
|
}
|
|
83
|
+
|
|
83
84
|
if (searchOptions.includes('search-api-params')) {
|
|
84
|
-
stringToSearch = `${stringToSearch} ${path.parameters && path.parameters.map(
|
|
85
|
+
stringToSearch = `${stringToSearch} ${path.parameters && path.parameters.map(v => v.name).join(' ') || ''}`;
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
if (searchOptions.includes('search-api-request-body') && path.requestBody) {
|
|
88
89
|
let schemaKeySet = new Set();
|
|
90
|
+
|
|
89
91
|
for (const contentType in path.requestBody && path.requestBody.content) {
|
|
90
92
|
if (path.requestBody.content[contentType].schema && path.requestBody.content[contentType].schema.properties) {
|
|
91
93
|
schemaKeySet = schemaKeys(path.requestBody.content[contentType].schema.properties);
|
|
92
94
|
}
|
|
95
|
+
|
|
93
96
|
stringToSearch = `${stringToSearch} ${[...schemaKeySet].join(' ')}`;
|
|
94
97
|
}
|
|
95
98
|
}
|
|
96
99
|
|
|
97
100
|
if (searchOptions.includes('search-api-resp-descr')) {
|
|
98
|
-
stringToSearch = `${stringToSearch} ${Object.values(path.responses).map(
|
|
101
|
+
stringToSearch = `${stringToSearch} ${Object.values(path.responses).map(v => v.description || '').join(' ')}`;
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
if (stringToSearch.toLowerCase().includes(searchVal.trim().toLowerCase())) {
|
|
@@ -104,27 +107,24 @@ export function advancedSearch(searchVal, allSpecTags, searchOptions = []) {
|
|
|
104
107
|
method: path.method,
|
|
105
108
|
path: path.path,
|
|
106
109
|
summary: path.summary || path.description || '',
|
|
107
|
-
deprecated: path.deprecated
|
|
110
|
+
deprecated: path.deprecated
|
|
108
111
|
});
|
|
109
112
|
}
|
|
110
113
|
});
|
|
111
114
|
});
|
|
112
115
|
return pathsMatched;
|
|
113
116
|
}
|
|
114
|
-
|
|
115
117
|
export function getCurrentElement() {
|
|
116
118
|
const currentQuery = (window.location.hash || '').split('?')[1];
|
|
117
119
|
const query = new URLSearchParams(currentQuery);
|
|
118
120
|
return decodeURIComponent(query.get('route') || '');
|
|
119
121
|
}
|
|
120
|
-
|
|
121
122
|
export function replaceState(rawElementId) {
|
|
122
123
|
const elementId = rawElementId && rawElementId.replace(/^#/, '') || '';
|
|
123
|
-
|
|
124
124
|
const currentNavigationHashPart = (window.location.hash || '').split('?')[0].replace(/^#/, '');
|
|
125
125
|
const currentQuery = (window.location.hash || '').split('?')[1];
|
|
126
126
|
const query = new URLSearchParams(currentQuery);
|
|
127
127
|
query.delete('route');
|
|
128
128
|
const newQuery = query.toString().length > 1 ? `${query.toString()}&route=${elementId}` : `route=${elementId}`;
|
|
129
129
|
window.history.replaceState(null, null, `#${currentNavigationHashPart}?${newQuery}`);
|
|
130
|
-
}
|
|
130
|
+
}
|