rapidoc 9.3.7 → 10.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.
Files changed (69) hide show
  1. package/README.md +193 -61
  2. package/dist/rapidoc-min.js +279 -3912
  3. package/package.json +24 -47
  4. package/.babelrc.json +0 -42
  5. package/.eslintrc +0 -53
  6. package/.github/FUNDING.yml +0 -13
  7. package/.github/workflow/words.yaml +0 -22
  8. package/CONTRIBUTING.md +0 -35
  9. package/dist/index.html +0 -6
  10. package/dist/index.html.gz +0 -0
  11. package/dist/rapidoc-min.js.LICENSE.txt +0 -64
  12. package/dist/rapidoc-min.js.LICENSE.txt.gz +0 -0
  13. package/dist/rapidoc-min.js.gz +0 -0
  14. package/dist/rapidoc-min.js.map +0 -1
  15. package/dist/rapidoc-min.js.map.gz +0 -0
  16. package/dist/rapidoc.js +0 -21594
  17. package/dist/report.html +0 -39
  18. package/index-hashroute.html +0 -65
  19. package/index.html +0 -249
  20. package/index1.html +0 -244
  21. package/jsconfig.json +0 -11
  22. package/logo.png +0 -0
  23. package/performance.html +0 -31
  24. package/src/components/api-request.js +0 -1787
  25. package/src/components/api-response.js +0 -335
  26. package/src/components/dialog-box.js +0 -73
  27. package/src/components/json-tree.js +0 -145
  28. package/src/components/schema-table.js +0 -358
  29. package/src/components/schema-tree.js +0 -378
  30. package/src/components/tag-input.js +0 -135
  31. package/src/index.js +0 -7
  32. package/src/json-schema-viewer.js +0 -345
  33. package/src/oauth-receiver.js +0 -56
  34. package/src/rapidoc-mini.js +0 -380
  35. package/src/rapidoc.js +0 -1082
  36. package/src/styles/border-styles.js +0 -24
  37. package/src/styles/custom-styles.js +0 -10
  38. package/src/styles/dialog-box-styles.js +0 -75
  39. package/src/styles/endpoint-styles.js +0 -202
  40. package/src/styles/flex-styles.js +0 -15
  41. package/src/styles/font-styles.js +0 -268
  42. package/src/styles/info-styles.js +0 -21
  43. package/src/styles/input-styles.js +0 -202
  44. package/src/styles/nav-styles.js +0 -198
  45. package/src/styles/prism-styles.js +0 -105
  46. package/src/styles/schema-styles.js +0 -121
  47. package/src/styles/tab-styles.js +0 -47
  48. package/src/styles/table-styles.js +0 -58
  49. package/src/templates/advance-search-template.js +0 -72
  50. package/src/templates/callback-template.js +0 -73
  51. package/src/templates/code-samples-template.js +0 -34
  52. package/src/templates/components-template.js +0 -76
  53. package/src/templates/endpoint-template.js +0 -249
  54. package/src/templates/expanded-endpoint-template.js +0 -197
  55. package/src/templates/focused-endpoint-template.js +0 -110
  56. package/src/templates/header-template.js +0 -66
  57. package/src/templates/json-schema-viewer-template.js +0 -185
  58. package/src/templates/logo-template.js +0 -18
  59. package/src/templates/main-body-template.js +0 -121
  60. package/src/templates/navbar-template.js +0 -272
  61. package/src/templates/overview-template.js +0 -79
  62. package/src/templates/security-scheme-template.js +0 -610
  63. package/src/templates/server-template.js +0 -115
  64. package/src/utils/color-utils.js +0 -59
  65. package/src/utils/common-utils.js +0 -168
  66. package/src/utils/schema-utils.js +0 -1086
  67. package/src/utils/spec-parser.js +0 -462
  68. package/src/utils/theme.js +0 -288
  69. package/webpack.config.js +0 -142
@@ -1,1787 +0,0 @@
1
- import { LitElement, html, css } from 'lit';
2
- import { unsafeHTML } from 'lit/directives/unsafe-html.js'; // eslint-disable-line import/extensions
3
- import { guard } from 'lit/directives/guard.js'; // eslint-disable-line import/extensions
4
- import { live } from 'lit/directives/live.js'; // eslint-disable-line import/extensions
5
- import { ifDefined } from 'lit/directives/if-defined.js'; // eslint-disable-line import/extensions
6
- import { marked } from 'marked';
7
- import formatXml from 'xml-but-prettier';
8
- import Prism from 'prismjs';
9
- import TableStyles from '~/styles/table-styles';
10
- import FlexStyles from '~/styles/flex-styles';
11
- import InputStyles from '~/styles/input-styles';
12
- import FontStyles from '~/styles/font-styles';
13
- import BorderStyles from '~/styles/border-styles';
14
- import TabStyles from '~/styles/tab-styles';
15
- import PrismStyles from '~/styles/prism-styles';
16
- import CustomStyles from '~/styles/custom-styles';
17
- import { copyToClipboard, downloadResource, viewResource } from '~/utils/common-utils';
18
- import { schemaInObjectNotation,
19
- getTypeInfo,
20
- generateExample,
21
- normalizeExamples,
22
- getSchemaFromParam,
23
- json2xml,
24
- standardizeExample,
25
- anyExampleWithSummaryOrDescription } from '~/utils/schema-utils';
26
- import '~/components/json-tree';
27
- import '~/components/schema-tree';
28
- import '~/components/tag-input';
29
-
30
- export default class ApiRequest extends LitElement {
31
- constructor() {
32
- super();
33
- this.responseMessage = '';
34
- this.responseStatus = 'success';
35
- this.responseHeaders = '';
36
- this.responseText = '';
37
- this.responseUrl = '';
38
- this.curlSyntax = '';
39
- this.activeResponseTab = 'response'; // allowed values: response, headers, curl
40
- this.selectedRequestBodyType = '';
41
- this.selectedRequestBodyExample = '';
42
- this.activeParameterSchemaTabs = {};
43
- }
44
-
45
- static get properties() {
46
- return {
47
- serverUrl: { type: String, attribute: 'server-url' },
48
- servers: { type: Array },
49
- method: { type: String },
50
- path: { type: String },
51
- security: { type: Array },
52
- parameters: { type: Array },
53
- request_body: { type: Object },
54
- api_keys: { type: Array },
55
- parser: { type: Object },
56
- accept: { type: String },
57
- callback: { type: String },
58
- webhook: { type: String },
59
- responseMessage: { type: String, attribute: false },
60
- responseText: { type: String, attribute: false },
61
- responseHeaders: { type: String, attribute: false },
62
- responseStatus: { type: String, attribute: false },
63
- responseUrl: { type: String, attribute: false },
64
- curlSyntax: { type: String, attribute: false },
65
- fillRequestFieldsWithExample: { type: String, attribute: 'fill-request-fields-with-example' },
66
- allowTry: { type: String, attribute: 'allow-try' },
67
- showCurlBeforeTry: { type: String, attribute: 'show-curl-before-try' },
68
- renderStyle: { type: String, attribute: 'render-style' },
69
- schemaStyle: { type: String, attribute: 'schema-style' },
70
- activeSchemaTab: { type: String, attribute: 'active-schema-tab' },
71
- activeParameterSchemaTabs: {
72
- type: Object,
73
- converter: {
74
- fromAttribute: (attr) => JSON.parse(attr),
75
- toAttribute: (prop) => JSON.stringify(prop),
76
- },
77
- attribute: 'active-parameter-schema-tabs',
78
- },
79
- schemaExpandLevel: { type: Number, attribute: 'schema-expand-level' },
80
- schemaDescriptionExpanded: { type: String, attribute: 'schema-description-expanded' },
81
- allowSchemaDescriptionExpandToggle: { type: String, attribute: 'allow-schema-description-expand-toggle' },
82
- schemaHideReadOnly: { type: String, attribute: 'schema-hide-read-only' },
83
- schemaHideWriteOnly: { type: String, attribute: 'schema-hide-write-only' },
84
- fetchCredentials: { type: String, attribute: 'fetch-credentials' },
85
-
86
- // properties for internal tracking
87
- activeResponseTab: { type: String }, // internal tracking of response-tab not exposed as a attribute
88
- selectedRequestBodyType: { type: String, attribute: 'selected-request-body-type' }, // internal tracking of selected request-body type
89
- selectedRequestBodyExample: { type: String, attribute: 'selected-request-body-example' }, // internal tracking of selected request-body example
90
- };
91
- }
92
-
93
- static get styles() {
94
- return [
95
- TableStyles,
96
- InputStyles,
97
- FontStyles,
98
- FlexStyles,
99
- BorderStyles,
100
- TabStyles,
101
- PrismStyles,
102
- css`
103
- :host {
104
- container-type: inline-size;
105
- }
106
- *, *:before, *:after { box-sizing: border-box; }
107
- :where(button, input[type="checkbox"], [tabindex="0"]):focus-visible { box-shadow: var(--focus-shadow); }
108
- :where(input[type="text"], input[type="password"], select, textarea):focus-visible { border-color: var(--primary-color); }
109
- tag-input:focus-within { outline: 1px solid;}
110
- .read-mode {
111
- margin-top: 24px;
112
- }
113
- .param-name,
114
- .param-type {
115
- margin: 1px 0;
116
- text-align: right;
117
- line-height: var(--font-size-small);
118
- }
119
- .param-name {
120
- color: var(--fg);
121
- font-family: var(--font-mono);
122
- }
123
- .param-name.deprecated {
124
- color: var(--red);
125
- }
126
- .param-type{
127
- color: var(--light-fg);
128
- font-family: var(--font-regular);
129
- }
130
- .param-constraint{
131
- min-width:100px;
132
- }
133
- .param-constraint:empty{
134
- display:none;
135
- }
136
- .top-gap{margin-top:24px;}
137
-
138
- .textarea {
139
- min-height:220px;
140
- padding:5px;
141
- resize:vertical;
142
- direction: ltr;
143
- }
144
- .example:first-child {
145
- margin-top: -9px;
146
- }
147
-
148
- .response-message{
149
- font-weight:bold;
150
- text-overflow: ellipsis;
151
- }
152
- .response-message.error {
153
- color:var(--red);
154
- }
155
- .response-message.success {
156
- color:var(--blue);
157
- }
158
-
159
- .file-input-container {
160
- align-items:flex-end;
161
- }
162
- .file-input-container .input-set:first-child .file-input-remove-btn{
163
- visibility:hidden;
164
- }
165
-
166
- .file-input-remove-btn{
167
- font-size:16px;
168
- color:var(--red);
169
- outline: none;
170
- border: none;
171
- background:none;
172
- cursor:pointer;
173
- }
174
-
175
- .v-tab-btn {
176
- font-size: var(--smal-font-size);
177
- height:24px;
178
- border:none;
179
- background:none;
180
- opacity: 0.3;
181
- cursor: pointer;
182
- padding: 4px 8px;
183
- }
184
- .v-tab-btn.active {
185
- font-weight: bold;
186
- background: var(--bg);
187
- opacity: 1;
188
- }
189
-
190
- @container (min-width: 768px) {
191
- .textarea {
192
- padding:8px;
193
- }
194
- }
195
-
196
- @container (max-width: 470px) {
197
- .hide-in-small-screen {
198
- display:none;
199
- }
200
- }
201
- `,
202
- CustomStyles,
203
- ];
204
- }
205
-
206
- render() {
207
- return html`
208
- <div class="col regular-font request-panel ${'read focused'.includes(this.renderStyle) || this.callback === 'true' ? 'read-mode' : 'view-mode'}">
209
- <div class=" ${this.callback === 'true' ? 'tiny-title' : 'req-res-title'} ">
210
- ${this.callback === 'true' ? 'CALLBACK REQUEST' : 'REQUEST'}
211
- </div>
212
- <div>
213
- ${guard([this.method, this.path, this.allowTry, this.parameters, this.activeParameterSchemaTabs], () => this.inputParametersTemplate('path'))}
214
- ${guard([this.method, this.path, this.allowTry, this.parameters, this.activeParameterSchemaTabs], () => this.inputParametersTemplate('query'))}
215
- ${this.requestBodyTemplate()}
216
- ${guard([this.method, this.path, this.allowTry, this.parameters, this.activeParameterSchemaTabs], () => this.inputParametersTemplate('header'))}
217
- ${guard([this.method, this.path, this.allowTry, this.parameters, this.activeParameterSchemaTabs], () => this.inputParametersTemplate('cookie'))}
218
- ${this.allowTry === 'false' ? '' : html`${this.apiCallTemplate()}`}
219
- </div>
220
- </div>
221
- `;
222
- }
223
-
224
- async updated() {
225
- if (this.showCurlBeforeTry === 'true') {
226
- this.applyCURLSyntax(this.shadowRoot);
227
- }
228
-
229
- // In focused mode after rendering the request component, update the text-areas(which contains examples) using
230
- // the original values from hidden textareas
231
- // This is done coz, user may update the dom by editing the textarea's and once the DOM is updated externally change detection wont happen, therefore update the values manually
232
-
233
- // if (this.renderStyle === 'focused') {
234
- // if (changedProperties.size === 1 && changedProperties.has('activeSchemaTab')) {
235
- // // dont update example as only tabs is switched
236
- // } else {
237
- // this.requestUpdate();
238
- // }
239
- // }
240
-
241
- if (this.webhook === 'true') {
242
- this.allowTry = 'false';
243
- }
244
- }
245
-
246
- async saveExampleState() {
247
- if (this.renderStyle === 'focused') {
248
- const reqBodyTextAreaEls = [...this.shadowRoot.querySelectorAll('textarea.request-body-param-user-input')];
249
- reqBodyTextAreaEls.forEach((el) => {
250
- el.dataset.user_example = el.value;
251
- });
252
- const exampleTextAreaEls = [...this.shadowRoot.querySelectorAll('textarea[data-ptype="form-data"]')];
253
- exampleTextAreaEls.forEach((el) => {
254
- el.dataset.user_example = el.value;
255
- });
256
- this.requestUpdate();
257
- }
258
- }
259
-
260
- async updateExamplesFromDataAttr() {
261
- // In focused mode after rendering the request component, update the text-areas(which contains examples) using
262
- // the original values from hidden textareas
263
- // This is done coz, user may update the dom by editing the textarea's and once the DOM is updated externally change detection wont happen, therefore update the values manually
264
- if (this.renderStyle === 'focused') {
265
- const reqBodyTextAreaEls = [...this.shadowRoot.querySelectorAll('textarea.request-body-param-user-input')];
266
- reqBodyTextAreaEls.forEach((el) => {
267
- el.value = el.dataset.user_example || el.dataset.example;
268
- });
269
- const exampleTextAreaEls = [...this.shadowRoot.querySelectorAll('textarea[data-ptype="form-data"]')];
270
- exampleTextAreaEls.forEach((el) => {
271
- el.value = el.dataset.user_example || el.dataset.example;
272
- });
273
- this.requestUpdate();
274
- }
275
- }
276
-
277
- /* eslint-disable indent */
278
- renderExample(example, paramType, paramName) {
279
- return html`
280
- ${paramType === 'array' ? '[' : ''}
281
- <a
282
- part="anchor anchor-param-example"
283
- style="display:inline-block; min-width:24px; text-align:center"
284
- class="${this.allowTry === 'true' ? '' : 'inactive-link'}"
285
- data-example-type="${paramType === 'array' ? paramType : 'string'}"
286
- data-example="${example.value && Array.isArray(example.value) ? example.value?.join('~|~') : (typeof example.value === 'object' ? JSON.stringify(example.value, null, 2) : example.value) || ''}"
287
- title="${example.value && Array.isArray(example.value) ? example.value?.join('~|~') : (typeof example.value === 'object' ? JSON.stringify(example.value, null, 2) : example.value) || ''}"
288
- @click="${(e) => {
289
- const inputEl = e.target.closest('table').querySelector(`[data-pname="${paramName}"]`);
290
- if (inputEl) {
291
- inputEl.value = e.target.dataset.exampleType === 'array' ? e.target.dataset.example.split('~|~') : e.target.dataset.example;
292
- }
293
- }}"
294
- > ${example.printableValue || example.value} </a>
295
- ${paramType === 'array' ? '] ' : ''}
296
- `;
297
- }
298
-
299
- renderShortFormatExamples(examples, paramType, paramName) {
300
- return html`${examples.map((x, i) => html`
301
- ${i === 0 ? '' : '┃'}
302
- ${this.renderExample(x, paramType, paramName)}`)}`;
303
- }
304
-
305
- renderLongFormatExamples(exampleList, paramType, paramName) {
306
- return html` <ul style="list-style-type: disclosure-closed;">
307
- ${exampleList.map((v) => html`
308
- <li>
309
- ${this.renderExample(v, paramType, paramName)}
310
- ${v.summary?.length > 0 ? html`<span>&lpar;${v.summary}&rpar;</span>` : ''}
311
- ${v.description?.length > 0 ? html`<p>${unsafeHTML(marked(v.description))}</p>` : ''}
312
- </li>
313
- `)}
314
- </ul>`;
315
- }
316
-
317
- exampleListTemplate(paramName, paramType, exampleList = []) {
318
- return html` ${
319
- exampleList.length > 0
320
- ? html`<span style="font-weight:bold">Examples: </span>
321
- ${anyExampleWithSummaryOrDescription(exampleList)
322
- ? this.renderLongFormatExamples(exampleList, paramType, paramName)
323
- : this.renderShortFormatExamples(exampleList, paramType, paramName)}`
324
- : ''
325
- }`;
326
- }
327
-
328
- inputParametersTemplate(paramType) {
329
- const filteredParams = this.parameters ? this.parameters.filter((param) => param.in === paramType) : [];
330
- if (filteredParams.length === 0) {
331
- return '';
332
- }
333
- let title = '';
334
- if (paramType === 'path') {
335
- title = 'PATH PARAMETERS';
336
- } else if (paramType === 'query') {
337
- title = 'QUERY-STRING PARAMETERS';
338
- } else if (paramType === 'header') {
339
- title = 'REQUEST HEADERS';
340
- } else if (paramType === 'cookie') {
341
- title = 'COOKIES';
342
- }
343
-
344
- const tableRows = [];
345
- for (const param of filteredParams) {
346
- const [declaredParamSchema, serializeStyle, mimeTypeElem] = getSchemaFromParam(param);
347
- if (!declaredParamSchema) {
348
- continue;
349
- }
350
- const paramSchema = getTypeInfo(declaredParamSchema);
351
- if (!paramSchema) {
352
- continue; // eslint-disable-line no-continue
353
- }
354
- const schemaAsObj = schemaInObjectNotation(declaredParamSchema, {});
355
- // let exampleVal = '';
356
- // let exampleList = [];
357
- let paramStyle = 'form';
358
- let paramExplode = true;
359
- let paramAllowReserved = false;
360
- if (paramType === 'query' || paramType === 'header' || paramType === 'path') {
361
- if (param.style && 'form spaceDelimited pipeDelimited'.includes(param.style)) {
362
- paramStyle = param.style;
363
- } else if (serializeStyle) {
364
- paramStyle = serializeStyle;
365
- }
366
- if (typeof param.explode === 'boolean') {
367
- paramExplode = param.explode;
368
- }
369
- if (typeof param.allowReserved === 'boolean') {
370
- paramAllowReserved = param.allowReserved;
371
- }
372
- }
373
- // openapi 3.1.0 spec based examples (which must be Object(string : { value:any, summary?: string, description?: string})
374
- const example = normalizeExamples(
375
- (standardizeExample(param.examples)
376
- || standardizeExample(param.example)
377
- || standardizeExample(mimeTypeElem?.example)
378
- || standardizeExample(mimeTypeElem?.examples)
379
- || standardizeExample(paramSchema.examples)
380
- || standardizeExample(paramSchema.example)
381
- ),
382
- paramSchema.type,
383
- );
384
- if (!example.exampleVal && paramSchema.type === 'object') {
385
- example.exampleVal = generateExample(
386
- declaredParamSchema,
387
- serializeStyle || 'json',
388
- {},
389
- {},
390
- this.callback === 'true' || this.webhook === 'true' ? true : false, // eslint-disable-line no-unneeded-ternary
391
- this.callback === 'true' || this.webhook === 'true' ? false : true, // eslint-disable-line no-unneeded-ternary
392
- true,
393
- 'text',
394
- false,
395
- )[0].exampleValue;
396
- }
397
- const labelColWidth = 'read focused'.includes(this.renderStyle) ? '200px' : '160px';
398
- tableRows.push(html`
399
- <tr title="${param.deprecated ? 'Deprecated' : ''}">
400
- <td rowspan="${this.allowTry === 'true' ? '1' : '2'}" style="width:${labelColWidth}; min-width:100px;">
401
- <div class="param-name ${param.deprecated ? 'deprecated' : ''}" >
402
- ${param.deprecated ? html`<span style='color:var(--red);'>✗</span>` : ''}
403
- ${param.required ? html`<span style='color:var(--red)'>*</span>` : ''}
404
- ${param.name}
405
- </div>
406
- <div class="param-type">
407
- ${paramSchema.type === 'array'
408
- ? `${paramSchema.arrayType}`
409
- : `${paramSchema.format ? paramSchema.format : paramSchema.type}`
410
- }
411
- </div>
412
- </td>
413
- ${this.allowTry === 'true'
414
- ? html`
415
- <td style="min-width:100px;" colspan="${paramSchema.default || paramSchema.constrain || paramSchema.allowedValues || paramSchema.pattern ? '1' : '2'}">
416
- ${paramSchema.type === 'array'
417
- ? html`
418
- <tag-input class="request-param"
419
- id = "tag-input-request-param-${param.name}"
420
- style = "width:100%"
421
- data-ptype = "${paramType}"
422
- data-pname = "${param.name}"
423
- data-example = "${Array.isArray(example.exampleVal) ? example.exampleVal.join('~|~') : example.exampleVal}"
424
- data-param-serialize-style = "${paramStyle}"
425
- data-param-serialize-explode = "${paramExplode}"
426
- data-param-allow-reserved = "${paramAllowReserved}"
427
- data-x-fill-example = "${param['x-fill-example'] || 'yes'}"
428
- data-array = "true"
429
- placeholder = "add-multiple &#x21a9;"
430
- .value="${param['x-fill-example'] === 'no'
431
- ? []
432
- : live(this.fillRequestFieldsWithExample === 'true' ? Array.isArray(example.exampleVal) ? example.exampleVal : [example.exampleVal] : [])
433
- }"
434
- >
435
- </tag-input>`
436
- : paramSchema.type === 'object'
437
- ? html`
438
- <div class="tab-panel col" style="border-width:0 0 1px 0;">
439
- <div class="tab-buttons row" @click="${(e) => {
440
- if (e.target.tagName.toLowerCase() === 'button') {
441
- const newState = { ...this.activeParameterSchemaTabs };
442
- newState[param.name] = e.target.dataset.tab;
443
- this.activeParameterSchemaTabs = newState;
444
- }
445
- }}">
446
- <button class="tab-btn ${this.activeParameterSchemaTabs[param.name] === 'example' ? 'active' : ''}" data-tab = 'example'>EXAMPLE </button>
447
- <button class="tab-btn ${this.activeParameterSchemaTabs[param.name] !== 'example' ? 'active' : ''}" data-tab = 'schema'>SCHEMA</button>
448
- </div>
449
-
450
- ${html`<div class="tab-content col" data-tab = 'example' style="display:${this.activeParameterSchemaTabs[param.name] === 'example' ? 'block' : 'none'}; padding-left:5px; width:100%">
451
- <textarea
452
- id = "textarea-request-param-${param.name}"
453
- class = "textarea request-param"
454
- part = "textarea textarea-param"
455
- data-ptype = "${paramType}-object"
456
- data-pname = "${param.name}"
457
- data-example = "${example.exampleVal}"
458
- data-param-serialize-style = "${paramStyle}"
459
- data-param-serialize-explode = "${paramExplode}"
460
- data-param-allow-reserved = "${paramAllowReserved}"
461
- data-x-fill-example = "${param['x-fill-example'] || 'yes'}"
462
- spellcheck = "false"
463
- .textContent="${param['x-fill-example'] === 'no' ? '' : live(this.fillRequestFieldsWithExample === 'true' ? (typeof example.exampleVal === 'object' ? JSON.stringify(example.exampleVal, null, 2) : example.exampleVal) : '')}"
464
- style = "resize:vertical; width:100%; height: ${'read focused'.includes(this.renderStyle) ? '180px' : '120px'};"
465
- @input=${(e) => {
466
- const requestPanelEl = this.getRequestPanel(e);
467
- this.liveCURLSyntaxUpdate(requestPanelEl);
468
- }}
469
- ></textarea>
470
- </div>`
471
- }
472
- ${html`<div class="tab-content col" data-tab = 'schema' style="display:${this.activeParameterSchemaTabs[param.name] !== 'example' ? 'block' : 'none'}; padding-left:5px; width:100%;">
473
- <schema-tree
474
- class = 'json'
475
- style = 'display: block'
476
- .data = '${schemaAsObj}'
477
- schema-expand-level = "${this.schemaExpandLevel}"
478
- schema-description-expanded = "${this.schemaDescriptionExpanded}"
479
- allow-schema-description-expand-toggle = "${this.allowSchemaDescriptionExpandToggle}"
480
- schema-hide-read-only = "${this.schemaHideReadOnly.includes(this.method)}"
481
- schema-hide-write-only = "${this.schemaHideWriteOnly.includes(this.method)}"
482
- exportparts = "wrap-request-btn:wrap-request-btn, btn:btn, btn-fill:btn-fill, btn-outline:btn-outline, btn-try:btn-try, btn-clear:btn-clear, btn-clear-resp:btn-clear-resp,
483
- file-input:file-input, textbox:textbox, textbox-param:textbox-param, textarea:textarea, textarea-param:textarea-param,
484
- anchor:anchor, anchor-param-example:anchor-param-example"
485
- > </schema-tree>
486
- </div>`
487
- }
488
- </div>`
489
- : html`
490
- <input type="${paramSchema.format === 'password' ? 'password' : 'text'}" spellcheck="false" style="width:100%"
491
- id="input-request-param-${param.name}"
492
- class="request-param"
493
- part="textbox textbox-param"
494
- data-ptype="${paramType}"
495
- data-pname="${param.name}"
496
- data-example="${Array.isArray(example.exampleVal) ? example.exampleVal.join('~|~') : example.exampleVal}"
497
- data-param-allow-reserved = "${paramAllowReserved}"
498
- data-x-fill-example = "${param['x-fill-example'] || 'yes'}"
499
- data-array="false"
500
- .value="${param['x-fill-example'] === 'no' ? '' : live(this.fillRequestFieldsWithExample === 'true' ? example.exampleVal : '')}"
501
- @input=${(e) => {
502
- const requestPanelEl = this.getRequestPanel(e);
503
- this.liveCURLSyntaxUpdate(requestPanelEl);
504
- }}
505
- />`
506
- }
507
- </td>`
508
- : ''
509
- }
510
- ${paramSchema.default || paramSchema.constrain || paramSchema.allowedValues || paramSchema.pattern
511
- ? html`
512
- <td colspan="${(this.allowTry === 'true') ? '1' : '2'}">
513
- <div class="param-constraint">
514
- ${paramSchema.default ? html`<span style="font-weight:bold">Default: </span>${paramSchema.default}<br/>` : ''}
515
- ${paramSchema.pattern ? html`<span style="font-weight:bold">Pattern: </span>${paramSchema.pattern}<br/>` : ''}
516
- ${paramSchema.constrain ? html`${paramSchema.constrain}<br/>` : ''}
517
- ${paramSchema.allowedValues && paramSchema.allowedValues.split('┃').map((v, i) => html`
518
- ${i > 0 ? '┃' : html`<span style="font-weight:bold">Allowed: </span>`}
519
- ${html`
520
- <a part="anchor anchor-param-constraint" class = "${this.allowTry === 'true' ? '' : 'inactive-link'}"
521
- data-type="${paramSchema.type === 'array' ? paramSchema.type : 'string'}"
522
- data-enum="${v.trim()}"
523
- @click="${(e) => {
524
- const inputEl = e.target.closest('table').querySelector(`[data-pname="${param.name}"]`);
525
- if (inputEl) {
526
- if (e.target.dataset.type === 'array') {
527
- inputEl.value = [e.target.dataset.enum];
528
- } else {
529
- inputEl.value = e.target.dataset.enum;
530
- }
531
- }
532
- }}"
533
- >${v}</a>`
534
- }`)}
535
- </div>
536
- </td>`
537
- : html`<td></td>`
538
- }
539
- </tr>
540
- <tr>
541
- ${this.allowTry === 'true' ? html`<td style="border:none"> </td>` : ''}
542
- <td colspan="2" style="border:none">
543
- <span class="m-markdown-small">${unsafeHTML(marked(param.description || ''))}</span>
544
- ${this.exampleListTemplate.call(this, param.name, paramSchema.type, example.exampleList)}
545
- </td>
546
- </tr>
547
- `);
548
- }
549
-
550
- return html`
551
- <div class="table-title top-gap">${title}</div>
552
- <div style="display:block; overflow-x:auto; max-width:100%;">
553
- <table role="presentation" class="m-table" style="width:100%; word-break:break-word;">
554
- ${tableRows}
555
- </table>
556
- </div>`;
557
- }
558
-
559
- // This method is called before navigation change in focused mode
560
- async beforeNavigationFocusedMode() {
561
- // this.saveExampleState();
562
- }
563
-
564
- // This method is called after navigation change in focused mode
565
- async afterNavigationFocusedMode() {
566
- this.selectedRequestBodyType = '';
567
- this.selectedRequestBodyExample = '';
568
- this.updateExamplesFromDataAttr();
569
- this.clearResponseData();
570
- }
571
-
572
- // Request-Body Event Handlers
573
- onSelectExample(e) {
574
- this.selectedRequestBodyExample = e.target.value;
575
- const exampleDropdownEl = e.target;
576
- window.setTimeout((selectEl) => {
577
- const readOnlyExampleEl = selectEl.closest('.example-panel').querySelector('.request-body-param');
578
- const userInputExampleTextareaEl = selectEl.closest('.example-panel').querySelector('.request-body-param-user-input');
579
- userInputExampleTextareaEl.value = readOnlyExampleEl.innerText;
580
-
581
- const requestPanelEl = this.getRequestPanel({ target: selectEl });
582
- this.liveCURLSyntaxUpdate(requestPanelEl);
583
- }, 0, exampleDropdownEl);
584
- }
585
-
586
- onMimeTypeChange(e) {
587
- this.selectedRequestBodyType = e.target.value;
588
- const mimeDropdownEl = e.target;
589
- this.selectedRequestBodyExample = '';
590
- window.setTimeout((selectEl) => {
591
- const readOnlyExampleEl = selectEl.closest('.request-body-container').querySelector('.request-body-param');
592
- if (readOnlyExampleEl) {
593
- const userInputExampleTextareaEl = selectEl.closest('.request-body-container').querySelector('.request-body-param-user-input');
594
- userInputExampleTextareaEl.value = readOnlyExampleEl.innerText;
595
- }
596
- }, 0, mimeDropdownEl);
597
- }
598
-
599
- requestBodyTemplate() {
600
- if (!this.request_body) {
601
- return '';
602
- }
603
- if (Object.keys(this.request_body).length === 0) {
604
- return '';
605
- }
606
-
607
- // Variable to store partial HTMLs
608
- let reqBodyTypeSelectorHtml = '';
609
- let reqBodyFileInputHtml = '';
610
- let reqBodyFormHtml = '';
611
- let reqBodySchemaHtml = '';
612
- let reqBodyExampleHtml = '';
613
-
614
- const requestBodyTypes = [];
615
- const { content } = this.request_body;
616
- for (const mimeType in content) {
617
- requestBodyTypes.push({
618
- mimeType,
619
- schema: content[mimeType].schema,
620
- example: content[mimeType].example,
621
- examples: content[mimeType].examples,
622
- });
623
- if (!this.selectedRequestBodyType) {
624
- this.selectedRequestBodyType = mimeType;
625
- }
626
- }
627
- // MIME Type selector
628
- reqBodyTypeSelectorHtml = requestBodyTypes.length === 1
629
- ? ''
630
- : html`
631
- <select style="min-width:100px; max-width:100%; margin-bottom:-1px;" @change = '${(e) => this.onMimeTypeChange(e)}'>
632
- ${requestBodyTypes.map((reqBody) => html`
633
- <option value = '${reqBody.mimeType}' ?selected = '${reqBody.mimeType === this.selectedRequestBodyType}'>
634
- ${reqBody.mimeType}
635
- </option> `)
636
- }
637
- </select>
638
- `;
639
-
640
- // For Loop - Main
641
- requestBodyTypes.forEach((reqBody) => {
642
- let schemaAsObj;
643
- let reqBodyExamples = [];
644
-
645
- if (this.selectedRequestBodyType.includes('json') || this.selectedRequestBodyType.includes('xml') || this.selectedRequestBodyType.includes('text') || this.selectedRequestBodyType.includes('jose')) {
646
- // Generate Example
647
- if (reqBody.mimeType === this.selectedRequestBodyType) {
648
- reqBodyExamples = generateExample(
649
- reqBody.schema,
650
- reqBody.mimeType,
651
- standardizeExample(reqBody.examples),
652
- standardizeExample(reqBody.example),
653
- this.callback === 'true' || this.webhook === 'true' ? true : false, // eslint-disable-line no-unneeded-ternary
654
- this.callback === 'true' || this.webhook === 'true' ? false : true, // eslint-disable-line no-unneeded-ternary
655
- 'text',
656
- false,
657
- );
658
- if (!this.selectedRequestBodyExample) {
659
- this.selectedRequestBodyExample = (reqBodyExamples.length > 0 ? reqBodyExamples[0].exampleId : '');
660
- }
661
- reqBodyExampleHtml = html`
662
- ${reqBodyExampleHtml}
663
- <div class = 'example-panel border-top pad-top-8'>
664
- ${reqBodyExamples.length === 1
665
- ? ''
666
- : html`
667
- <select style="min-width:100px; max-width:100%; margin-bottom:-1px;" @change='${(e) => this.onSelectExample(e)}'>
668
- ${reqBodyExamples.map((v) => html`<option value="${v.exampleId}" ?selected=${v.exampleId === this.selectedRequestBodyExample} >
669
- ${v.exampleSummary.length > 80 ? v.exampleId : v.exampleSummary ? v.exampleSummary : v.exampleId}
670
- </option>`)}
671
- </select>
672
- `
673
- }
674
- ${reqBodyExamples
675
- .filter((v) => v.exampleId === this.selectedRequestBodyExample)
676
- .map((v) => html`
677
- <div class="example ${v.exampleId === this.selectedRequestBodyExample ? 'example-selected' : ''}" data-example = '${v.exampleId}'>
678
- ${v.exampleSummary && v.exampleSummary.length > 80 ? html`<div style="padding: 4px 0"> ${v.exampleSummary} </div>` : ''}
679
- ${v.exampleDescription ? html`<div class="m-markdown-small" style="padding: 4px 0"> ${unsafeHTML(marked(v.exampleDescription || ''))} </div>` : ''}
680
- <!-- This pre(hidden) is to store the original example value, this will remain unchanged when users switches from one example to another, its is used to populate the editable textarea -->
681
- <pre
682
- class = "textarea is-hidden request-body-param ${reqBody.mimeType.substring(reqBody.mimeType.indexOf('/') + 1)}"
683
- spellcheck = "false"
684
- data-ptype = "${reqBody.mimeType}"
685
- style="width:100%; resize:vertical; display:none"
686
- >${(v.exampleFormat === 'text' ? v.exampleValue : JSON.stringify(v.exampleValue, null, 2))}</pre>
687
-
688
- <!-- this textarea is for user to edit the example -->
689
- <textarea
690
- class = "textarea request-body-param-user-input"
691
- part = "textarea textarea-param"
692
- spellcheck = "false"
693
- data-ptype = "${reqBody.mimeType}"
694
- data-example = "${v.exampleFormat === 'text' ? v.exampleValue : JSON.stringify(v.exampleValue, null, 2)}"
695
- data-example-format = "${v.exampleFormat}"
696
- style="width:100%; resize:vertical;"
697
- .textContent = "${this.fillRequestFieldsWithExample === 'true' ? (v.exampleFormat === 'text' ? v.exampleValue : JSON.stringify(v.exampleValue, null, 2)) : ''}"
698
- @input=${(e) => {
699
- const requestPanelEl = this.getRequestPanel(e);
700
- this.liveCURLSyntaxUpdate(requestPanelEl);
701
- }}
702
- @keydown=${(e) => {
703
- if ((e.keyCode === 10 || e.keyCode === 13) && e.ctrlKey) {
704
- return this.onTryClick(e);
705
- }
706
- }}
707
-
708
- ></textarea>
709
- </div>
710
- `)}
711
-
712
- </div>
713
- `;
714
- }
715
- } else if (this.selectedRequestBodyType.includes('form-urlencoded') || this.selectedRequestBodyType.includes('form-data')) {
716
- if (reqBody.mimeType === this.selectedRequestBodyType) {
717
- const ex = generateExample(
718
- reqBody.schema,
719
- reqBody.mimeType,
720
- reqBody.examples,
721
- reqBody.example,
722
- this.callback === 'true' || this.webhook === 'true' ? true : false, // eslint-disable-line no-unneeded-ternary
723
- this.callback === 'true' || this.webhook === 'true' ? false : true, // eslint-disable-line no-unneeded-ternary
724
- 'text',
725
- false,
726
- );
727
- if (reqBody.schema) {
728
- reqBodyFormHtml = this.formDataTemplate(reqBody.schema, reqBody.mimeType, (ex[0] ? ex[0].exampleValue : ''));
729
- }
730
- }
731
- } else if ((/^audio\/|^image\/|^video\/|^font\/|tar$|zip$|7z$|rtf$|msword$|excel$|\/pdf$|\/octet-stream$/.test(this.selectedRequestBodyType))) {
732
- if (reqBody.mimeType === this.selectedRequestBodyType) {
733
- reqBodyFileInputHtml = html`
734
- <div class = "small-font-size bold-text row">
735
- <input id="input-request-body-param-file" type="file" part="file-input" style="max-width:100%" class="request-body-param-file" data-ptype="${reqBody.mimeType}" spellcheck="false" />
736
- </div>
737
- `;
738
- }
739
- }
740
-
741
- // Generate Schema
742
- if (reqBody.mimeType.includes('json') || reqBody.mimeType.includes('xml') || reqBody.mimeType.includes('text') || this.selectedRequestBodyType.includes('jose')) {
743
- schemaAsObj = schemaInObjectNotation(reqBody.schema, {});
744
- if (this.schemaStyle === 'table') {
745
- reqBodySchemaHtml = html`
746
- ${reqBodySchemaHtml}
747
- <schema-table
748
- class = '${reqBody.mimeType.substring(reqBody.mimeType.indexOf('/') + 1)}'
749
- style = 'display: ${this.selectedRequestBodyType === reqBody.mimeType ? 'block' : 'none'};'
750
- .data = '${schemaAsObj}'
751
- schema-expand-level = "${this.schemaExpandLevel}"
752
- schema-description-expanded = "${this.schemaDescriptionExpanded}"
753
- allow-schema-description-expand-toggle = "${this.allowSchemaDescriptionExpandToggle}"
754
- schema-hide-read-only = "${this.schemaHideReadOnly}"
755
- schema-hide-write-only = "${this.schemaHideWriteOnly}"
756
- exportparts = "schema-description:schema-description, schema-multiline-toggle:schema-multiline-toggle"
757
- > </schema-table>
758
- `;
759
- } else if (this.schemaStyle === 'tree') {
760
- reqBodySchemaHtml = html`
761
- ${reqBodySchemaHtml}
762
- <schema-tree
763
- class = "${reqBody.mimeType.substring(reqBody.mimeType.indexOf('/') + 1)}"
764
- style = "display: ${this.selectedRequestBodyType === reqBody.mimeType ? 'block' : 'none'};"
765
- .data = "${schemaAsObj}"
766
- schema-expand-level = "${this.schemaExpandLevel}"
767
- schema-description-expanded = "${this.schemaDescriptionExpanded}"
768
- allow-schema-description-expand-toggle = "${this.allowSchemaDescriptionExpandToggle}"
769
- schema-hide-read-only = "${this.schemaHideReadOnly}"
770
- schema-hide-write-only = "${this.schemaHideWriteOnly}"
771
- exportparts = "schema-description:schema-description, schema-multiline-toggle:schema-multiline-toggle"
772
- > </schema-tree>
773
- `;
774
- }
775
- }
776
- });
777
-
778
- return html`
779
- <div class='request-body-container' data-selected-request-body-type="${this.selectedRequestBodyType}">
780
- <div class="table-title top-gap row">
781
- REQUEST BODY ${this.request_body.required ? html`<span class="mono-font" style='color:var(--red)'>*</span>` : ''}
782
- <span style = "font-weight:normal; margin-left:5px"> ${this.selectedRequestBodyType}</span>
783
- <span style="flex:1"></span>
784
- ${reqBodyTypeSelectorHtml}
785
- </div>
786
- ${this.request_body.description ? html`<div class="m-markdown" style="margin-bottom:12px">${unsafeHTML(marked(this.request_body.description))}</div>` : ''}
787
-
788
- ${(this.selectedRequestBodyType.includes('json') || this.selectedRequestBodyType.includes('xml') || this.selectedRequestBodyType.includes('text') || this.selectedRequestBodyType.includes('jose'))
789
- ? html`
790
- <div class="tab-panel col" style="border-width:0 0 1px 0;">
791
- <div class="tab-buttons row" @click="${(e) => { if (e.target.tagName.toLowerCase() === 'button') { this.activeSchemaTab = e.target.dataset.tab; } }}">
792
- <button class="tab-btn ${this.activeSchemaTab === 'example' ? 'active' : ''}" data-tab = 'example'>EXAMPLE</button>
793
- <button class="tab-btn ${this.activeSchemaTab !== 'example' ? 'active' : ''}" data-tab = 'schema'>SCHEMA</button>
794
- </div>
795
- ${html`<div class="tab-content col" style="display:${this.activeSchemaTab === 'example' ? 'block' : 'none'};"> ${reqBodyExampleHtml}</div>`}
796
- ${html`<div class="tab-content col" style="display:${this.activeSchemaTab === 'example' ? 'none' : 'block'};"> ${reqBodySchemaHtml}</div>`}
797
- </div>`
798
- : html`
799
- ${reqBodyFileInputHtml}
800
- ${reqBodyFormHtml}`
801
- }
802
- </div>
803
- `;
804
- }
805
-
806
- formDataParamAsObjectTemplate(fieldName, fieldSchema, mimeType) {
807
- // This template is used when form-data param should be send as a object (application/json, application/xml)
808
- const formdataPartSchema = schemaInObjectNotation(fieldSchema, {});
809
- const formdataPartExample = generateExample(
810
- fieldSchema,
811
- 'json',
812
- standardizeExample(fieldSchema.examples),
813
- standardizeExample(fieldSchema.example),
814
- this.callback === 'true' || this.webhook === 'true' ? true : false, // eslint-disable-line no-unneeded-ternary
815
- this.callback === 'true' || this.webhook === 'true' ? false : true, // eslint-disable-line no-unneeded-ternary
816
- 'text',
817
- false,
818
- );
819
-
820
- return html`
821
- <div class="tab-panel row" style="min-height:220px; border-left: 6px solid var(--light-border-color); align-items: stretch;">
822
- <div style="width:24px; background-color:var(--light-border-color)">
823
- <div class="row" style="flex-direction:row-reverse; width:160px; height:24px; transform:rotate(270deg) translateX(-160px); transform-origin:top left; display:block;" @click="${(e) => {
824
- if (e.target.classList.contains('v-tab-btn')) {
825
- const { tab } = e.target.dataset;
826
- if (tab) {
827
- const tabPanelEl = e.target.closest('.tab-panel');
828
- const selectedTabBtnEl = tabPanelEl.querySelector(`.v-tab-btn[data-tab="${tab}"]`);
829
- const otherTabBtnEl = [...tabPanelEl.querySelectorAll(`.v-tab-btn:not([data-tab="${tab}"])`)];
830
- const selectedTabContentEl = tabPanelEl.querySelector(`.tab-content[data-tab="${tab}"]`);
831
- const otherTabContentEl = [...tabPanelEl.querySelectorAll(`.tab-content:not([data-tab="${tab}"])`)];
832
- selectedTabBtnEl.classList.add('active');
833
- selectedTabContentEl.style.display = 'block';
834
- otherTabBtnEl.forEach((el) => { el.classList.remove('active'); });
835
- otherTabContentEl.forEach((el) => { el.style.display = 'none'; });
836
- }
837
- }
838
- if (e.target.tagName.toLowerCase() === 'button') { this.activeSchemaTab = e.target.dataset.tab; }
839
- }}">
840
- <button class="v-tab-btn ${this.activeSchemaTab === 'example' ? 'active' : ''}" data-tab = 'example'>EXAMPLE</button>
841
- <button class="v-tab-btn ${this.activeSchemaTab !== 'example' ? 'active' : ''}" data-tab = 'schema'>SCHEMA</button>
842
- </div>
843
- </div>
844
- ${html`
845
- <div class="tab-content col" data-tab = 'example' style="display:${this.activeSchemaTab === 'example' ? 'block' : 'none'}; padding-left:5px; width:100%">
846
- <textarea
847
- class = "textarea"
848
- part = "textarea textarea-param"
849
- style = "width:100%; border:none; resize:vertical;"
850
- data-array = "false"
851
- data-ptype = "${mimeType.includes('form-urlencode') ? 'form-urlencode' : 'form-data'}"
852
- data-pname = "${fieldName}"
853
- data-example = "${formdataPartExample[0]?.exampleValue || ''}"
854
- .textContent = "${this.fillRequestFieldsWithExample === 'true' ? formdataPartExample[0].exampleValue : ''}"
855
- spellcheck = "false"
856
- ></textarea>
857
- </div>`
858
- }
859
- ${html`
860
- <div class="tab-content col" data-tab = 'schema' style="display:${this.activeSchemaTab !== 'example' ? 'block' : 'none'}; padding-left:5px; width:100%;">
861
- <schema-tree
862
- .data = "${formdataPartSchema}"
863
- schema-expand-level = "${this.schemaExpandLevel}"
864
- schema-description-expanded = "${this.schemaDescriptionExpanded}"
865
- allow-schema-description-expand-toggle = "${this.allowSchemaDescriptionExpandToggle}",
866
- > </schema-tree>
867
- </div>`
868
- }
869
- </div>
870
- `;
871
- }
872
-
873
- formDataTemplate(schema, mimeType, exampleValue = '') {
874
- const formDataTableRows = [];
875
- if (schema.properties) {
876
- for (const fieldName in schema.properties) {
877
- const fieldSchema = schema.properties[fieldName];
878
- if (fieldSchema.readOnly) {
879
- continue;
880
- }
881
- const fieldExamples = fieldSchema.examples || fieldSchema.example || '';
882
- const fieldType = fieldSchema.type;
883
- const paramSchema = getTypeInfo(fieldSchema);
884
- const labelColWidth = 'read focused'.includes(this.renderStyle) ? '200px' : '160px';
885
- const example = normalizeExamples((paramSchema.examples || paramSchema.example), paramSchema.type);
886
- formDataTableRows.push(html`
887
- <tr title="${fieldSchema.deprecated ? 'Deprecated' : ''}">
888
- <td style="width:${labelColWidth}; min-width:100px;">
889
- <div class="param-name ${fieldSchema.deprecated ? 'deprecated' : ''}">
890
- ${fieldName}${(schema.required?.includes(fieldName) || fieldSchema.required) ? html`<span style='color:var(--red);'>*</span>` : ''}
891
- </div>
892
- <div class="param-type">${paramSchema.type}</div>
893
- </td>
894
- <td
895
- style="${fieldType === 'object' ? 'width:100%; padding:0;' : this.allowTry === 'true' ? '' : 'display:none;'} min-width:100px;"
896
- colspan="${fieldType === 'object' ? 2 : 1}">
897
- ${fieldType === 'array'
898
- ? fieldSchema.items?.format === 'binary'
899
- ? html`
900
- <div class="file-input-container col" style='align-items:flex-end;' @click="${(e) => this.onAddRemoveFileInput(e, fieldName, mimeType)}">
901
- <div class='input-set row'>
902
- <input
903
- type = "file"
904
- part = "file-input"
905
- style = "width:100%"
906
- data-pname = "${fieldName}"
907
- data-ptype = "${mimeType.includes('form-urlencode') ? 'form-urlencode' : 'form-data'}"
908
- data-array = "false"
909
- data-file-array = "true"
910
- />
911
- <button class="file-input-remove-btn"> &#x2715; </button>
912
- </div>
913
- <button class="m-btn primary file-input-add-btn" part="btn btn-fill" style="margin:2px 25px 0 0; padding:2px 6px;">ADD</button>
914
- </div>
915
- `
916
- : html`
917
- <tag-input
918
- style = "width:100%"
919
- data-ptype = "${mimeType.includes('form-urlencode') ? 'form-urlencode' : 'form-data'}"
920
- data-pname = "${fieldName}"
921
- data-example = "${Array.isArray(fieldExamples) ? fieldExamples.join('~|~') : fieldExamples}"
922
- data-array = "true"
923
- placeholder = "add-multiple &#x21a9;"
924
- .value = "${Array.isArray(fieldExamples) ? Array.isArray(fieldExamples[0]) ? fieldExamples[0] : fieldExamples : []}"
925
- >
926
- </tag-input>
927
- `
928
- : html`
929
- ${fieldType === 'object'
930
- ? this.formDataParamAsObjectTemplate.call(this, fieldName, fieldSchema, mimeType)
931
- : html`
932
- ${this.allowTry === 'true'
933
- ? html`<input
934
- .value = "${this.fillRequestFieldsWithExample === 'true' ? example.exampleVal : ''}"
935
- spellcheck = "false"
936
- type = "${fieldSchema.format === 'binary' ? 'file' : fieldSchema.format === 'password' ? 'password' : 'text'}"
937
- part = "textbox textbox-param"
938
- style = "width:100%"
939
- data-ptype = "${mimeType.includes('form-urlencode') ? 'form-urlencode' : 'form-data'}"
940
- data-pname = "${fieldName}"
941
- data-example = "${Array.isArray(fieldExamples) ? fieldExamples[0] : fieldExamples}"
942
- data-array = "false"
943
- />`
944
- : ''
945
- }
946
- `
947
- }`
948
- }
949
- </td>
950
- ${fieldType === 'object'
951
- ? ''
952
- : html`
953
- <td>
954
- ${paramSchema.default || paramSchema.constrain || paramSchema.allowedValues || paramSchema.pattern
955
- ? html`
956
- <div class="param-constraint">
957
- ${paramSchema.default ? html`<span style="font-weight:bold">Default: </span>${paramSchema.default}<br/>` : ''}
958
- ${paramSchema.pattern ? html`<span style="font-weight:bold">Pattern: </span>${paramSchema.pattern}<br/>` : ''}
959
- ${paramSchema.constrain ? html`${paramSchema.constrain}<br/>` : ''}
960
- ${paramSchema.allowedValues && paramSchema.allowedValues.split('┃').map((v, i) => html`
961
- ${i > 0 ? '┃' : html`<span style="font-weight:bold">Allowed: </span>`}
962
- ${html`
963
- <a part="anchor anchor-param-constraint" class = "${this.allowTry === 'true' ? '' : 'inactive-link'}"
964
- data-type="${paramSchema.type === 'array' ? paramSchema.type : 'string'}"
965
- data-enum="${v.trim()}"
966
- @click="${(e) => {
967
- const inputEl = e.target.closest('table').querySelector(`[data-pname="${fieldName}"]`);
968
- if (inputEl) {
969
- if (e.target.dataset.type === 'array') {
970
- inputEl.value = [e.target.dataset.enum];
971
- } else {
972
- inputEl.value = e.target.dataset.enum;
973
- }
974
- }
975
- }}"
976
- >
977
- ${v}
978
- </a>`
979
- }`)
980
- }
981
- </div>`
982
- : ''
983
- }
984
- </td>`
985
- }
986
- </tr>
987
- ${fieldType === 'object'
988
- ? ''
989
- : html`
990
- <tr>
991
- <td style="border:none"> </td>
992
- <td colspan="2" style="border:none; margin-top:0; padding:0 5px 8px 5px;">
993
- <span class="m-markdown-small">${unsafeHTML(marked(fieldSchema.description || ''))}</span>
994
- ${this.exampleListTemplate.call(this, fieldName, paramSchema.type, example.exampleList)}
995
- </td>
996
- </tr>
997
- `
998
- }`);
999
- }
1000
- return html`
1001
- <table role="presentation" style="width:100%;" class="m-table">
1002
- ${formDataTableRows}
1003
- </table>
1004
- `;
1005
- }
1006
-
1007
- return html`
1008
- <textarea
1009
- class = "textarea dynamic-form-param ${mimeType}"
1010
- part = "textarea textarea-param"
1011
- spellcheck = "false"
1012
- data-pname="dynamic-form"
1013
- data-ptype="${mimeType}"
1014
- .textContent = "${exampleValue}"
1015
- style="width:100%"
1016
- ></textarea>
1017
- ${schema.description ? html`<span class="m-markdown-small">${unsafeHTML(marked(schema.description))}</span>` : ''}
1018
- `;
1019
- }
1020
-
1021
- curlSyntaxTemplate(display = 'flex') {
1022
- return html`
1023
- <div class="col m-markdown" style="flex:1; display:${display}; position:relative; max-width: 100%;">
1024
- <button class="toolbar-btn" style = "position:absolute; top:12px; right:8px" @click='${(e) => { copyToClipboard(this.curlSyntax.trim().replace(/\\$/, ''), e); }}' part="btn btn-fill"> Copy </button>
1025
- <pre style="white-space:pre"><code>${unsafeHTML(Prism.highlight(this.curlSyntax.trim().replace(/\\$/, ''), Prism.languages.shell, 'shell'))}</code></pre>
1026
- </div>
1027
- `;
1028
- }
1029
-
1030
- apiResponseTabTemplate() {
1031
- let responseFormat = '';
1032
- let responseContent = '';
1033
- if (!this.responseIsBlob) {
1034
- if (this.responseHeaders.includes('application/x-ndjson')) {
1035
- responseFormat = 'json';
1036
- const prismLines = this.responseText.split('\n').map((q) => Prism.highlight(q, Prism.languages[responseFormat], responseFormat)).join('\n');
1037
- responseContent = html`<code>${unsafeHTML(prismLines)}</code>`;
1038
- } else if (this.responseHeaders.includes('json')) {
1039
- responseFormat = 'json';
1040
- responseContent = html`<code>${unsafeHTML(Prism.highlight(this.responseText, Prism.languages[responseFormat], responseFormat))}</code>`;
1041
- } else if (this.responseHeaders.includes('html') || this.responseHeaders.includes('xml')) {
1042
- responseFormat = 'html';
1043
- responseContent = html`<code>${unsafeHTML(Prism.highlight(this.responseText, Prism.languages[responseFormat], responseFormat))}</code>`;
1044
- } else {
1045
- responseFormat = 'text';
1046
- responseContent = html`<code>${this.responseText}</code>`;
1047
- }
1048
- }
1049
- return html`
1050
- <div class="row" style="font-size:var(--font-size-small); margin:5px 0">
1051
- <div class="response-message ${this.responseStatus}">Response Status: ${this.responseMessage}</div>
1052
- <div style="flex:1"></div>
1053
- <button class="m-btn" part="btn btn-outline btn-clear-response" @click="${this.clearResponseData}">CLEAR RESPONSE</button>
1054
- </div>
1055
- <div class="tab-panel col" style="border-width:0 0 1px 0;">
1056
- <div id="tab_buttons" class="tab-buttons row" @click="${(e) => {
1057
- if (e.target.classList.contains('tab-btn') === false) { return; }
1058
- this.activeResponseTab = e.target.dataset.tab;
1059
- }}">
1060
- <button class="tab-btn ${this.activeResponseTab === 'response' ? 'active' : ''}" data-tab = 'response' > RESPONSE</button>
1061
- <button class="tab-btn ${this.activeResponseTab === 'headers' ? 'active' : ''}" data-tab = 'headers' > RESPONSE HEADERS</button>
1062
- ${this.showCurlBeforeTry === 'true'
1063
- ? ''
1064
- : html`<button class="tab-btn ${this.activeResponseTab === 'curl' ? 'active' : ''}" data-tab = 'curl'>CURL</button>`}
1065
- </div>
1066
- ${this.responseIsBlob
1067
- ? html`
1068
- <div class="tab-content col" style="flex:1; display:${this.activeResponseTab === 'response' ? 'flex' : 'none'};">
1069
- ${this.responseBlobType === 'image'
1070
- ? html`<img style="max-height:var(--resp-area-height, 400px); object-fit:contain;" class="mar-top-8" src="${ifDefined(this.responseBlobUrl)}"></img>`
1071
- : ''
1072
- }
1073
- <button class="m-btn thin-border mar-top-8" style="width:135px" @click='${(e) => { downloadResource(this.responseBlobUrl, this.respContentDisposition, e); }}' part="btn btn-outline">
1074
- DOWNLOAD
1075
- </button>
1076
- ${this.responseBlobType === 'view' || this.responseBlobType === 'image'
1077
- ? html`<button class="m-btn thin-border mar-top-8" style="width:135px" @click='${(e) => { viewResource(this.responseBlobUrl, e); }}' part="btn btn-outline">VIEW (NEW TAB)</button>`
1078
- : ''
1079
- }
1080
- </div>`
1081
- : html`
1082
- <div class="tab-content col m-markdown" style="flex:1; display:${this.activeResponseTab === 'response' ? 'flex' : 'none'};" >
1083
- <button class="toolbar-btn" style="position:absolute; top:12px; right:8px" @click='${(e) => { copyToClipboard(this.responseText, e); }}' part="btn btn-fill"> Copy </button>
1084
- <pre style="white-space:pre; min-height:50px; height:var(--resp-area-height, 400px); resize:vertical; overflow:auto">${responseContent}</pre>
1085
- </div>`
1086
- }
1087
- <div class="tab-content col m-markdown" style="flex:1; display:${this.activeResponseTab === 'headers' ? 'flex' : 'none'};" >
1088
- <button class="toolbar-btn" style = "position:absolute; top:12px; right:8px" @click='${(e) => { copyToClipboard(this.responseHeaders, e); }}' part="btn btn-fill"> Copy </button>
1089
- <pre style="white-space:pre"><code>${unsafeHTML(Prism.highlight(this.responseHeaders, Prism.languages.css, 'css'))}</code></pre>
1090
- </div>
1091
- ${this.showCurlBeforeTry === 'true' ? '' : this.curlSyntaxTemplate(this.activeResponseTab === 'curl' ? 'flex' : 'none')}
1092
- </div>`;
1093
- }
1094
-
1095
- apiCallTemplate() {
1096
- const selectedServerHtml = html`
1097
- <div style="display:flex; flex-direction:column;">
1098
- ${this.serverUrl
1099
- ? html`
1100
- <div style="display:flex; align-items:baseline;">
1101
- <div style="font-weight:bold; padding-right:5px;">API Server</div>
1102
- <span class = "gray-text"> ${this.serverUrl} </span>
1103
- </div>
1104
- `
1105
- : ''
1106
- }
1107
- </div>
1108
- `;
1109
-
1110
- return html`
1111
- <div style="display:flex; align-items:flex-end; margin:16px 0; font-size:var(--font-size-small);" part="wrap-request-btn">
1112
- <div class="hide-in-small-screen" style="flex-direction:column; margin:0; width:calc(100% - 60px);">
1113
- <div style="display:flex; flex-direction:row; align-items:center; overflow:hidden;">
1114
- ${selectedServerHtml}
1115
- </div>
1116
- <div style="display:flex;">
1117
- <div style="font-weight:bold; padding-right:5px;">Authentication</div>
1118
- ${this.security?.length > 0
1119
- ? html`
1120
- ${this.api_keys.length > 0
1121
- ? html`<div style="color:var(--blue); overflow:hidden;">
1122
- ${this.api_keys.length === 1
1123
- ? `${this.api_keys[0]?.typeDisplay} in ${this.api_keys[0].in}`
1124
- : `${this.api_keys.length} API keys applied`
1125
- }
1126
- </div>`
1127
- : html`<div class="gray-text">Required <span style="color:var(--red)">(None Applied)</span> </div>`
1128
- }`
1129
- : html`<span class="gray-text"> Not Required </span>`
1130
- }
1131
- </div>
1132
- </div>
1133
- ${
1134
- this.parameters.length > 0 || this.request_body
1135
- ? html`
1136
- <button class="m-btn thin-border" part="btn btn-outline btn-fill" style="margin-right:5px;" @click="${this.onFillRequestData}" title="Fills with example data (if provided)">
1137
- FILL EXAMPLE
1138
- </button>
1139
- <button class="m-btn thin-border" part="btn btn-outline btn-clear" style="margin-right:5px;" @click="${this.onClearRequestData}">
1140
- CLEAR
1141
- </button>`
1142
- : ''
1143
- }
1144
- <button class="m-btn primary thin-border" part="btn btn-try" @click="${this.onTryClick}">TRY</button>
1145
- </div>
1146
- <div class="row" style="font-size:var(--font-size-small); margin:5px 0">
1147
- ${this.showCurlBeforeTry === 'true' ? this.curlSyntaxTemplate() : ''}
1148
- </div>
1149
- ${this.responseMessage === '' ? '' : this.apiResponseTabTemplate()}
1150
- `;
1151
- }
1152
- /* eslint-enable indent */
1153
-
1154
- async onFillRequestData(e) {
1155
- const requestPanelEl = e.target.closest('.request-panel');
1156
- const requestPanelInputEls = [...requestPanelEl.querySelectorAll('input, tag-input, textarea:not(.is-hidden)')];
1157
- requestPanelInputEls.forEach((el) => {
1158
- if (el.dataset.example) {
1159
- if (el.tagName.toUpperCase() === 'TAG-INPUT') {
1160
- el.value = el.dataset.example.split('~|~');
1161
- } else {
1162
- el.value = el.dataset.example;
1163
- }
1164
- }
1165
- });
1166
- }
1167
-
1168
- async onClearRequestData(e) {
1169
- const requestPanelEl = e.target.closest('.request-panel');
1170
- const requestPanelInputEls = [...requestPanelEl.querySelectorAll('input, tag-input, textarea:not(.is-hidden)')];
1171
- requestPanelInputEls.forEach((el) => { el.value = ''; });
1172
- }
1173
-
1174
- buildFetchURL(requestPanelEl) {
1175
- let fetchUrl;
1176
- const pathParamEls = [...requestPanelEl.querySelectorAll("[data-ptype='path']")];
1177
- const queryParamEls = [...requestPanelEl.querySelectorAll("[data-ptype='query']")];
1178
- const queryParamObjTypeEls = [...requestPanelEl.querySelectorAll("[data-ptype='query-object']")];
1179
- fetchUrl = this.path;
1180
- // Generate URL using Path Params
1181
- pathParamEls.map((el) => {
1182
- fetchUrl = fetchUrl.replace(`{${el.dataset.pname}}`, encodeURIComponent(el.value));
1183
- });
1184
-
1185
- // Query Params
1186
- const urlQueryParamsMap = new Map();
1187
- const queryParamsWithReservedCharsAllowed = [];
1188
- if (queryParamEls.length > 0) {
1189
- queryParamEls.forEach((el) => {
1190
- const queryParam = new URLSearchParams();
1191
- if (el.dataset.paramAllowReserved === 'true') {
1192
- queryParamsWithReservedCharsAllowed.push(el.dataset.pname);
1193
- }
1194
- if (el.dataset.array === 'false') {
1195
- if (el.value !== '') {
1196
- queryParam.append(el.dataset.pname, el.value);
1197
- }
1198
- } else {
1199
- const { paramSerializeStyle, paramSerializeExplode } = el.dataset;
1200
- let vals = ((el.value && Array.isArray(el.value)) ? el.value : []);
1201
- vals = Array.isArray(vals) ? vals.filter((v) => v !== '') : [];
1202
- if (vals.length > 0) {
1203
- if (paramSerializeStyle === 'spaceDelimited') {
1204
- queryParam.append(el.dataset.pname, vals.join(' ').replace(/^\s|\s$/g, ''));
1205
- } else if (paramSerializeStyle === 'pipeDelimited') {
1206
- queryParam.append(el.dataset.pname, vals.join('|').replace(/^\||\|$/g, ''));
1207
- } else {
1208
- if (paramSerializeExplode === 'true') { // eslint-disable-line no-lonely-if
1209
- vals.forEach((v) => { queryParam.append(el.dataset.pname, v); });
1210
- } else {
1211
- queryParam.append(el.dataset.pname, vals.join(',').replace(/^,|,$/g, ''));
1212
- }
1213
- }
1214
- }
1215
- }
1216
- if (queryParam.toString()) {
1217
- urlQueryParamsMap.set(el.dataset.pname, queryParam);
1218
- }
1219
- });
1220
- }
1221
-
1222
- // Query Params (Dynamic - create from JSON)
1223
- if (queryParamObjTypeEls.length > 0) {
1224
- queryParamObjTypeEls.map((el) => {
1225
- const queryParam = new URLSearchParams();
1226
- try {
1227
- let queryParamObj = {};
1228
- const { paramSerializeStyle, paramSerializeExplode, pname } = el.dataset;
1229
- queryParamObj = Object.assign(queryParamObj, JSON.parse(el.value.replace(/\s+/g, ' ')));
1230
- if (el.dataset.paramAllowReserved === 'true') {
1231
- queryParamsWithReservedCharsAllowed.push(el.dataset.pname);
1232
- }
1233
- if ('json xml'.includes(paramSerializeStyle)) {
1234
- if (paramSerializeStyle === 'json') {
1235
- queryParam.append(el.dataset.pname, JSON.stringify(queryParamObj));
1236
- } else if (paramSerializeStyle === 'xml') {
1237
- queryParam.append(el.dataset.pname, json2xml(queryParamObj));
1238
- }
1239
- } else {
1240
- for (const key in queryParamObj) {
1241
- const pKey = `${pname}[${key}]`;
1242
- if (typeof queryParamObj[key] === 'object') {
1243
- if (Array.isArray(queryParamObj[key])) {
1244
- if (paramSerializeStyle === 'spaceDelimited') {
1245
- queryParam.append(pKey, queryParamObj[key].join(' '));
1246
- } else if (paramSerializeStyle === 'pipeDelimited') {
1247
- queryParam.append(pKey, queryParamObj[key].join('|'));
1248
- } else {
1249
- if (paramSerializeExplode === 'true') { // eslint-disable-line no-lonely-if
1250
- queryParamObj[key].forEach((v) => {
1251
- queryParam.append(pKey, v);
1252
- });
1253
- } else {
1254
- queryParam.append(pKey, queryParamObj[key]);
1255
- }
1256
- }
1257
- }
1258
- } else {
1259
- queryParam.append(pKey, queryParamObj[key]);
1260
- }
1261
- }
1262
- }
1263
- } catch {
1264
- console.error('RapiDoc: unable to parse %s into object', el.value); // eslint-disable-line no-console
1265
- }
1266
- if (queryParam.toString()) {
1267
- urlQueryParamsMap.set(el.dataset.pname, queryParam);
1268
- }
1269
- });
1270
- }
1271
- let urlQueryParamString = '';
1272
- if (urlQueryParamsMap.size) {
1273
- urlQueryParamsMap.forEach((val, pname) => {
1274
- if (queryParamsWithReservedCharsAllowed.includes(pname)) {
1275
- urlQueryParamString += `${pname}=`;
1276
- urlQueryParamString += val.getAll(pname).join(`&${pname}=`);
1277
- urlQueryParamString += '&';
1278
- } else {
1279
- urlQueryParamString += `${val.toString()}&`;
1280
- }
1281
- });
1282
- urlQueryParamString = urlQueryParamString.slice(0, -1);
1283
- }
1284
- if (urlQueryParamString.length !== 0) {
1285
- fetchUrl = `${fetchUrl}${fetchUrl.includes('?') ? '&' : '?'}${urlQueryParamString}`;
1286
- }
1287
-
1288
- // Add authentication Query-Param if provided
1289
- this.api_keys
1290
- .filter((v) => (v.in === 'query'))
1291
- .forEach((v) => {
1292
- fetchUrl = `${fetchUrl}${fetchUrl.includes('?') ? '&' : '?'}${v.name}=${encodeURIComponent(v.finalKeyValue)}`;
1293
- });
1294
-
1295
- fetchUrl = `${this.serverUrl.replace(/\/$/, '')}${fetchUrl}`;
1296
- return fetchUrl;
1297
- }
1298
-
1299
- buildFetchHeaders(requestPanelEl) {
1300
- const respEl = this.closest('.expanded-req-resp-container, .req-resp-container')?.getElementsByTagName('api-response')[0];
1301
- const headerParamEls = [...requestPanelEl.querySelectorAll("[data-ptype='header'], [data-ptype='header-object']")];
1302
- const requestBodyContainerEl = requestPanelEl.querySelector('.request-body-container');
1303
- const acceptHeader = respEl?.selectedMimeType;
1304
- const reqHeaders = new Headers();
1305
- if (acceptHeader) {
1306
- // Uses the acceptHeader from Response panel
1307
- reqHeaders.append('Accept', acceptHeader);
1308
- } else if (this.accept) {
1309
- reqHeaders.append('Accept', this.accept);
1310
- }
1311
-
1312
- // Add Authentication Header if provided
1313
- this.api_keys
1314
- .filter((v) => (v.in === 'header'))
1315
- .forEach((v) => {
1316
- reqHeaders.append(v.name, v.finalKeyValue);
1317
- });
1318
-
1319
- // Add Header Params
1320
- headerParamEls.map((el) => {
1321
- if (el.value) {
1322
- if (el.dataset.ptype === 'header-object') {
1323
- /* CONVERT
1324
- a header value from below object style
1325
- {
1326
- "key1": "val1",
1327
- "key2": {
1328
- "key2_1": "val2_1",
1329
- "key2_2": {
1330
- "key2_2_1": "val2_2_1"
1331
- }
1332
- },
1333
- "key3": "val3"
1334
- };
1335
-
1336
- TO >>>
1337
- key1=val1, key2={"key2_1":"val2_1","key2_2":{"key2_2_1":"val2_2_1"}}, key3=val3
1338
- */
1339
- const headerObjVal = JSON.parse(el.value.replace(/\n/g, '').trim());
1340
- const firstLevelKeySeparator = el.dataset.paramSerializeExplode === 'true' ? '=' : ',';
1341
- const headerStrVal = Object.keys(headerObjVal)
1342
- .map((key) => {
1343
- const value = headerObjVal[key];
1344
- if (typeof value === 'object') {
1345
- return `${key}${firstLevelKeySeparator}${JSON.stringify(value)}`;
1346
- }
1347
- return `${key}${firstLevelKeySeparator}${value}`;
1348
- })
1349
- .join(',');
1350
- reqHeaders.append(el.dataset.pname, headerStrVal);
1351
- } else {
1352
- reqHeaders.append(el.dataset.pname, el.value);
1353
- }
1354
- }
1355
- });
1356
-
1357
- if (requestBodyContainerEl) {
1358
- const requestBodyType = requestBodyContainerEl.dataset.selectedRequestBodyType;
1359
- // Common for all request-body
1360
- if (!requestBodyType.includes('form-data')) {
1361
- // For multipart/form-data dont set the content-type to allow creation of browser generated part boundaries
1362
- reqHeaders.append('Content-Type', requestBodyType);
1363
- }
1364
- }
1365
- return reqHeaders;
1366
- }
1367
-
1368
- buildFetchBodyOptions(requestPanelEl) {
1369
- const requestBodyContainerEl = requestPanelEl.querySelector('.request-body-container');
1370
- const fetchOptions = {
1371
- method: this.method.toUpperCase(),
1372
- };
1373
- if (requestBodyContainerEl) {
1374
- const requestBodyType = requestBodyContainerEl.dataset.selectedRequestBodyType;
1375
- if (requestBodyType.includes('form-urlencoded')) {
1376
- // url-encoded Form Params (dynamic) - Parse JSON and generate Params
1377
- const formUrlDynamicTextAreaEl = requestPanelEl.querySelector("[data-ptype='dynamic-form']");
1378
- if (formUrlDynamicTextAreaEl) {
1379
- const val = formUrlDynamicTextAreaEl.value;
1380
- const formUrlDynParams = new URLSearchParams();
1381
- let proceed = true;
1382
- let tmpObj;
1383
- if (val) {
1384
- try {
1385
- tmpObj = JSON.parse(val);
1386
- } catch (err) {
1387
- proceed = false;
1388
- console.warn('RapiDoc: Invalid JSON provided', err); // eslint-disable-line no-console
1389
- }
1390
- } else {
1391
- proceed = false;
1392
- }
1393
- if (proceed) {
1394
- for (const prop in tmpObj) {
1395
- formUrlDynParams.append(prop, JSON.stringify(tmpObj[prop]));
1396
- }
1397
- fetchOptions.body = formUrlDynParams;
1398
- }
1399
- } else {
1400
- // url-encoded Form Params (regular)
1401
- const formUrlEls = [...requestPanelEl.querySelectorAll("[data-ptype='form-urlencode']")];
1402
- const formUrlParams = new URLSearchParams();
1403
- formUrlEls
1404
- .filter((v) => (v.type !== 'file'))
1405
- .forEach((el) => {
1406
- if (el.dataset.array === 'false') {
1407
- if (el.value) {
1408
- formUrlParams.append(el.dataset.pname, el.value);
1409
- }
1410
- } else {
1411
- const vals = (el.value && Array.isArray(el.value)) ? el.value.join(',') : '';
1412
- formUrlParams.append(el.dataset.pname, vals);
1413
- }
1414
- });
1415
- fetchOptions.body = formUrlParams;
1416
- }
1417
- } else if (requestBodyType.includes('form-data')) {
1418
- const formDataParams = new FormData();
1419
- const formDataEls = [...requestPanelEl.querySelectorAll("[data-ptype='form-data']")];
1420
- formDataEls.forEach((el) => {
1421
- if (el.dataset.array === 'false') {
1422
- if (el.type === 'file' && el.files[0]) {
1423
- formDataParams.append(el.dataset.pname, el.files[0], el.files[0].name);
1424
- } else if (el.value) {
1425
- formDataParams.append(el.dataset.pname, el.value);
1426
- }
1427
- } else if (el.value && Array.isArray(el.value)) {
1428
- formDataParams.append(el.dataset.pname, el.value.join(','));
1429
- }
1430
- });
1431
- fetchOptions.body = formDataParams;
1432
- } else if (/^audio\/|^image\/|^video\/|^font\/|tar$|zip$|7z$|rtf$|msword$|excel$|\/pdf$|\/octet-stream$/.test(requestBodyType)) {
1433
- const bodyParamFileEl = requestPanelEl.querySelector('.request-body-param-file');
1434
- if (bodyParamFileEl?.files[0]) {
1435
- fetchOptions.body = bodyParamFileEl.files[0]; // eslint-disable-line prefer-destructuring
1436
- }
1437
- } else if (requestBodyType.includes('json') || requestBodyType.includes('xml') || requestBodyType.includes('text')) {
1438
- const exampleTextAreaEl = requestPanelEl.querySelector('.request-body-param-user-input');
1439
- if (exampleTextAreaEl?.value) {
1440
- fetchOptions.body = exampleTextAreaEl.value;
1441
- }
1442
- }
1443
- }
1444
-
1445
- return fetchOptions;
1446
- }
1447
-
1448
- async onTryClick(e) {
1449
- const tryBtnEl = e.target;
1450
- const requestPanelEl = tryBtnEl.closest('.request-panel');
1451
- const fetchUrl = this.buildFetchURL(requestPanelEl);
1452
- const fetchOptions = this.buildFetchBodyOptions(requestPanelEl);
1453
- const reqHeaders = this.buildFetchHeaders(requestPanelEl);
1454
- this.responseUrl = '';
1455
- this.responseHeaders = [];
1456
- this.curlSyntax = this.generateCURLSyntax(fetchUrl, reqHeaders, fetchOptions, requestPanelEl);
1457
- this.responseStatus = 'success';
1458
- this.responseIsBlob = false;
1459
-
1460
- this.respContentDisposition = '';
1461
- if (this.responseBlobUrl) {
1462
- URL.revokeObjectURL(this.responseBlobUrl);
1463
- this.responseBlobUrl = '';
1464
- }
1465
- if (this.fetchCredentials) {
1466
- fetchOptions.credentials = this.fetchCredentials;
1467
- }
1468
- const controller = new AbortController();
1469
- const { signal } = controller;
1470
- fetchOptions.headers = reqHeaders;
1471
- const tempRequest = { url: fetchUrl, ...fetchOptions };
1472
- this.dispatchEvent(new CustomEvent('before-try', {
1473
- bubbles: true,
1474
- composed: true,
1475
- detail: {
1476
- request: tempRequest,
1477
- controller,
1478
- },
1479
- }));
1480
- const updatedFetchOptions = {
1481
- method: tempRequest.method,
1482
- headers: tempRequest.headers,
1483
- credentials: tempRequest.credentials,
1484
- body: tempRequest.body,
1485
- };
1486
- const fetchRequest = new Request(tempRequest.url, updatedFetchOptions);
1487
-
1488
- let fetchResponse;
1489
- let responseClone;
1490
- try {
1491
- let respBlob;
1492
- let respJson;
1493
- let respText;
1494
- tryBtnEl.disabled = true;
1495
- this.responseText = '⌛';
1496
- this.responseMessage = '';
1497
- this.requestUpdate();
1498
- const startTime = performance.now();
1499
- fetchResponse = await fetch(fetchRequest, { signal });
1500
- const endTime = performance.now();
1501
- // Allow to modify response
1502
- /*
1503
- let resolveModifiedResponse; // Create a promise that will be resolved from the event listener
1504
- const modifiedResponsePromise = new Promise((resolve) => {
1505
- resolveModifiedResponse = resolve;
1506
- });
1507
- this.dispatchEvent(new CustomEvent('fetched-try', {
1508
- bubbles: true,
1509
- composed: true,
1510
- detail: {
1511
- request: fetchRequest,
1512
- response: fetchResponse,
1513
- resolveModifiedResponse, // pass the resolver function
1514
- },
1515
- }));
1516
- fetchResponse = await modifiedResponsePromise; // Wait for the modified response
1517
- */
1518
- responseClone = fetchResponse.clone(); // create a response clone to allow reading response body again (response.json, response.text etc)
1519
- tryBtnEl.disabled = false;
1520
- this.responseMessage = html`${fetchResponse.statusText ? `${fetchResponse.statusText}:${fetchResponse.status}` : fetchResponse.status} <div style="color:var(--light-fg)"> Took ${Math.round(endTime - startTime)} milliseconds </div>`;
1521
- this.responseUrl = fetchResponse.url;
1522
- const respHeadersObj = {};
1523
- fetchResponse.headers.forEach((hdrVal, hdr) => {
1524
- respHeadersObj[hdr] = hdrVal;
1525
- this.responseHeaders = `${this.responseHeaders}${hdr}: ${hdrVal}\n`;
1526
- });
1527
- const contentType = fetchResponse.headers.get('content-type').split(';')[0].trim();
1528
- const respEmpty = (await fetchResponse.clone().text()).length === 0;
1529
- if (respEmpty) {
1530
- this.responseText = '';
1531
- } else if (contentType) {
1532
- if (contentType === 'application/x-ndjson') {
1533
- this.responseText = await fetchResponse.text();
1534
- } else if (contentType.includes('json')) {
1535
- if ((/charset=[^"']+/).test(contentType)) {
1536
- const encoding = contentType.split('charset=')[1];
1537
- const buffer = await fetchResponse.arrayBuffer();
1538
- try {
1539
- respText = new TextDecoder(encoding).decode(buffer);
1540
- } catch {
1541
- respText = new TextDecoder('utf-8').decode(buffer);
1542
- }
1543
- try {
1544
- respJson = JSON.parse(respText);
1545
- this.responseText = JSON.stringify(respJson, null, 2);
1546
- } catch {
1547
- this.responseText = respText;
1548
- }
1549
- } else {
1550
- respJson = await fetchResponse.json();
1551
- this.responseText = JSON.stringify(respJson, null, 2);
1552
- }
1553
- // eslint-disable-next-line no-useless-escape
1554
- } else if (/^font\/|tar$|zip$|7z$|rtf$|msword$|excel$|\/pdf$|\/octet-stream$|^application\/vnd\./.test(contentType)) {
1555
- this.responseIsBlob = true;
1556
- this.responseBlobType = 'download';
1557
- } else if (/^image/.test(contentType)) {
1558
- this.responseIsBlob = true;
1559
- this.responseBlobType = 'image';
1560
- } else if (/^audio|^image|^video/.test(contentType)) {
1561
- this.responseIsBlob = true;
1562
- this.responseBlobType = 'view';
1563
- } else {
1564
- respText = await fetchResponse.text();
1565
- if (contentType.includes('xml')) {
1566
- this.responseText = formatXml(respText, { textNodesOnSameLine: true, indentor: ' ' });
1567
- } else {
1568
- this.responseText = respText;
1569
- }
1570
- }
1571
- if (this.responseIsBlob) {
1572
- const contentDisposition = fetchResponse.headers.get('content-disposition');
1573
- let filenameFromContentDeposition = 'filename';
1574
- const filenameStarRegexMatch = contentDisposition.match(/filename\*=\s*UTF-8''([^;]+)/); // Support Headers like >>> Content-Disposition: attachment; filename*=UTF-8''example%20file.pdf
1575
- if (filenameStarRegexMatch) {
1576
- filenameFromContentDeposition = decodeURIComponent(filenameStarRegexMatch[1]); // the filename* format in the Content-Disposition header follows RFC 5987, which allows encoding non-ASCII characters using percent encoding. so example%20file.pdf becomes example file.pdf
1577
- } else {
1578
- // Fallback to the regular filename format
1579
- const filenameMatch = contentDisposition.match(/filename="?([^"]+)"?/); // Content-Disposition: attachment; filename=example.pdf
1580
- if (filenameMatch) {
1581
- filenameFromContentDeposition = filenameMatch[1];
1582
- }
1583
- }
1584
- this.respContentDisposition = filenameFromContentDeposition;
1585
- respBlob = await fetchResponse.blob();
1586
- this.responseBlobUrl = URL.createObjectURL(respBlob);
1587
- }
1588
- } else {
1589
- respText = await fetchResponse.text();
1590
- this.responseText = respText;
1591
- }
1592
- this.dispatchEvent(new CustomEvent('after-try', {
1593
- bubbles: true,
1594
- composed: true,
1595
- detail: {
1596
- request: fetchRequest,
1597
- response: responseClone,
1598
- responseHeaders: respHeadersObj,
1599
- responseBody: respJson || respText || respBlob,
1600
- responseStatus: responseClone.ok,
1601
- },
1602
- }));
1603
- } catch (err) {
1604
- tryBtnEl.disabled = false;
1605
- if (err.name === 'AbortError') {
1606
- this.dispatchEvent(new CustomEvent('request-aborted', {
1607
- bubbles: true,
1608
- composed: true,
1609
- detail: {
1610
- err,
1611
- request: fetchRequest,
1612
- },
1613
- }));
1614
- this.responseMessage = 'Request Aborted';
1615
- this.responseText = 'Request Aborted';
1616
- } else {
1617
- this.dispatchEvent(new CustomEvent('after-try', {
1618
- bubbles: true,
1619
- composed: true,
1620
- detail: {
1621
- err,
1622
- request: fetchRequest,
1623
- },
1624
- }));
1625
- this.responseMessage = `${err.message} (CORS or Network Issue)`;
1626
- }
1627
- }
1628
- this.requestUpdate();
1629
- }
1630
-
1631
- liveCURLSyntaxUpdate(requestPanelEl) {
1632
- this.applyCURLSyntax(requestPanelEl);
1633
- this.requestUpdate();
1634
- }
1635
-
1636
- onGenerateCURLClick(e) {
1637
- const requestPanelEl = this.getRequestPanel(e);
1638
- this.applyCURLSyntax(requestPanelEl);
1639
- }
1640
-
1641
- getRequestPanel(e) {
1642
- return e.target.closest('.request-panel');
1643
- }
1644
-
1645
- applyCURLSyntax(requestPanelEl) {
1646
- const fetchUrl = this.buildFetchURL(requestPanelEl);
1647
- const fetchOptions = this.buildFetchBodyOptions(requestPanelEl);
1648
- const fetchHeaders = this.buildFetchHeaders(requestPanelEl);
1649
-
1650
- this.curlSyntax = this.generateCURLSyntax(fetchUrl, fetchHeaders, fetchOptions, requestPanelEl);
1651
- }
1652
-
1653
- generateCURLSyntax(fetchUrl, fetchHeaders, fetchOptions, requestPanelEl) {
1654
- let curlUrl;
1655
- let curl = '';
1656
- let curlHeaders = '';
1657
- let curlData = '';
1658
- let curlForm = '';
1659
- const requestBodyContainerEl = requestPanelEl.querySelector('.request-body-container');
1660
-
1661
- if (fetchUrl.startsWith('http') === false) {
1662
- const url = new URL(fetchUrl, window.location.href);
1663
- curlUrl = url.href;
1664
- } else {
1665
- curlUrl = fetchUrl;
1666
- }
1667
-
1668
- curl = `curl -X ${this.method.toUpperCase()} "${curlUrl}" \\\n`;
1669
-
1670
- fetchHeaders.forEach((value, key) => {
1671
- let tempHeaderArray = value.split(',');
1672
- tempHeaderArray = tempHeaderArray.map((el) => el.trim()).filter((string, index) => tempHeaderArray.indexOf(string) === index);
1673
- fetchHeaders.set(key, tempHeaderArray.join(', '));
1674
- });
1675
-
1676
- curlHeaders = Array.from(fetchHeaders).map(([key, value]) => ` -H '${key}: ${value}'`).join('\\\n');
1677
- if (curlHeaders) {
1678
- curlHeaders = `${curlHeaders} \\\n`;
1679
- }
1680
- if (fetchOptions.body instanceof URLSearchParams) {
1681
- curlData = ` -d ${fetchOptions.body.toString()} \\\n`;
1682
- } else if (fetchOptions.body instanceof File) {
1683
- curlData = ` --data-binary @${fetchOptions.body.name} \\\n`;
1684
- } else if (fetchOptions.body instanceof FormData) {
1685
- curlForm = Array.from(fetchOptions.body).reduce((aggregator, [key, value]) => {
1686
- if (value instanceof File) {
1687
- return [...aggregator, ` -F "${key}=@${value.name}"`];
1688
- }
1689
-
1690
- const multiple = value.match(/([^,],)/gm);
1691
-
1692
- if (multiple) {
1693
- const multipleResults = multiple.map((one) => `-F "${key}[]=${one}"`);
1694
-
1695
- return [...aggregator, ...multipleResults];
1696
- }
1697
-
1698
- return [...aggregator, ` -F "${key}=${value}"`];
1699
- }, []).join('\\\n');
1700
- } else if (requestBodyContainerEl && requestBodyContainerEl.dataset.selectedRequestBodyType) {
1701
- const requestBodyType = requestBodyContainerEl.dataset.selectedRequestBodyType;
1702
- const exampleTextAreaEl = requestPanelEl.querySelector('.request-body-param-user-input');
1703
- if (exampleTextAreaEl?.value) {
1704
- fetchOptions.body = exampleTextAreaEl.value;
1705
- if (requestBodyType.includes('json')) {
1706
- try {
1707
- curlData = ` -d '${JSON.stringify(JSON.parse(exampleTextAreaEl.value))}' \\\n`;
1708
- } catch {
1709
- // Ignore.
1710
- }
1711
- }
1712
- if (!curlData) {
1713
- curlData = ` -d '${exampleTextAreaEl.value.replace(/'/g, '\'"\'"\'')}' \\\n`;
1714
- }
1715
- }
1716
- }
1717
-
1718
- return `${curl}${curlHeaders}${curlData}${curlForm}`;
1719
- }
1720
-
1721
- onAddRemoveFileInput(e, pname, ptype) {
1722
- if (e.target.tagName.toLowerCase() !== 'button') {
1723
- return;
1724
- }
1725
-
1726
- if (e.target.classList.contains('file-input-remove-btn')) {
1727
- // Remove File Input Set
1728
- const el = e.target.closest('.input-set');
1729
- el.remove();
1730
- return;
1731
- }
1732
- const el = e.target.closest('.file-input-container');
1733
-
1734
- // Add File Input Set
1735
-
1736
- // Container
1737
- const newInputContainerEl = document.createElement('div');
1738
- newInputContainerEl.setAttribute('class', 'input-set row');
1739
-
1740
- // File Input
1741
- const newInputEl = document.createElement('input');
1742
- newInputEl.type = 'file';
1743
- newInputEl.style = 'width:200px; margin-top:2px;';
1744
- newInputEl.setAttribute('data-pname', pname);
1745
- newInputEl.setAttribute('data-ptype', ptype.includes('form-urlencode') ? 'form-urlencode' : 'form-data');
1746
- newInputEl.setAttribute('data-array', 'false');
1747
- newInputEl.setAttribute('data-file-array', 'true');
1748
-
1749
- // Remover Button
1750
- const newRemoveBtnEl = document.createElement('button');
1751
- newRemoveBtnEl.setAttribute('class', 'file-input-remove-btn');
1752
- newRemoveBtnEl.innerHTML = '&#x2715;';
1753
-
1754
- newInputContainerEl.appendChild(newInputEl);
1755
- newInputContainerEl.appendChild(newRemoveBtnEl);
1756
- el.insertBefore(newInputContainerEl, e.target);
1757
- // el.appendChild(newInputContainerEl);
1758
- }
1759
-
1760
- clearResponseData() {
1761
- this.responseUrl = '';
1762
- this.responseHeaders = '';
1763
- this.responseText = '';
1764
- this.responseStatus = 'success';
1765
- this.responseMessage = '';
1766
- this.responseIsBlob = false;
1767
- this.responseBlobType = '';
1768
- this.respContentDisposition = '';
1769
- if (this.responseBlobUrl) {
1770
- URL.revokeObjectURL(this.responseBlobUrl);
1771
- this.responseBlobUrl = '';
1772
- }
1773
- }
1774
-
1775
- disconnectedCallback() {
1776
- this.curlSyntax = '';
1777
- // Cleanup ObjectURL for the blob data if this component created one
1778
- if (this.responseBlobUrl) {
1779
- URL.revokeObjectURL(this.responseBlobUrl);
1780
- this.responseBlobUrl = '';
1781
- }
1782
- super.disconnectedCallback();
1783
- }
1784
- }
1785
-
1786
- // Register the element with the browser
1787
- customElements.define('api-request', ApiRequest);