rapidoc 9.3.8 → 10.0.1

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