rapidoc 9.3.2 → 9.3.4

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 (47) hide show
  1. package/.github/workflow/words.yaml +22 -0
  2. package/dist/index.html +1 -1
  3. package/dist/index.html.gz +0 -0
  4. package/dist/rapidoc-min.js +857 -738
  5. package/dist/rapidoc-min.js.LICENSE.txt +9 -10
  6. package/dist/rapidoc-min.js.LICENSE.txt.gz +0 -0
  7. package/dist/rapidoc-min.js.gz +0 -0
  8. package/dist/rapidoc-min.js.map +1 -1
  9. package/dist/rapidoc-min.js.map.gz +0 -0
  10. package/dist/rapidoc.js +9341 -25092
  11. package/dist/report.html +5 -4
  12. package/index.html +2 -3
  13. package/package.json +20 -19
  14. package/src/components/api-request.js +344 -154
  15. package/src/components/api-response.js +16 -12
  16. package/src/components/json-tree.js +17 -12
  17. package/src/components/schema-table.js +50 -42
  18. package/src/components/schema-tree.js +56 -53
  19. package/src/components/tag-input.js +7 -4
  20. package/src/json-schema-viewer.js +1 -2
  21. package/src/rapidoc-mini.js +1 -3
  22. package/src/rapidoc.js +112 -45
  23. package/src/styles/endpoint-styles.js +1 -1
  24. package/src/styles/font-styles.js +1 -1
  25. package/src/styles/info-styles.js +2 -1
  26. package/src/styles/input-styles.js +2 -16
  27. package/src/styles/nav-styles.js +57 -10
  28. package/src/styles/schema-styles.js +27 -15
  29. package/src/styles/tab-styles.js +1 -0
  30. package/src/templates/callback-template.js +6 -7
  31. package/src/templates/code-samples-template.js +1 -0
  32. package/src/templates/components-template.js +3 -5
  33. package/src/templates/endpoint-template.js +41 -27
  34. package/src/templates/expanded-endpoint-template.js +63 -13
  35. package/src/templates/focused-endpoint-template.js +4 -1
  36. package/src/templates/header-template.js +2 -2
  37. package/src/templates/json-schema-viewer-template.js +2 -1
  38. package/src/templates/main-body-template.js +19 -18
  39. package/src/templates/navbar-template.js +98 -81
  40. package/src/templates/overview-template.js +1 -1
  41. package/src/templates/security-scheme-template.js +48 -25
  42. package/src/templates/server-template.js +1 -1
  43. package/src/utils/common-utils.js +2 -5
  44. package/src/utils/schema-utils.js +208 -83
  45. package/src/utils/spec-parser.js +1 -0
  46. package/src/utils/theme.js +11 -5
  47. package/webpack.config.js +2 -1
@@ -3,6 +3,7 @@ import { unsafeHTML } from 'lit/directives/unsafe-html.js'; // eslint-disable-li
3
3
  import { guard } from 'lit/directives/guard.js'; // eslint-disable-line import/extensions
4
4
  import { live } from 'lit/directives/live.js'; // eslint-disable-line import/extensions
5
5
  import { marked } from 'marked';
6
+ import formatXml from 'xml-but-prettier';
6
7
  import Prism from 'prismjs';
7
8
  import TableStyles from '~/styles/table-styles';
8
9
  import FlexStyles from '~/styles/flex-styles';
@@ -12,8 +13,15 @@ import BorderStyles from '~/styles/border-styles';
12
13
  import TabStyles from '~/styles/tab-styles';
13
14
  import PrismStyles from '~/styles/prism-styles';
14
15
  import CustomStyles from '~/styles/custom-styles';
15
- import { copyToClipboard, prettyXml, downloadResource, viewResource } from '~/utils/common-utils';
16
- import { schemaInObjectNotation, getTypeInfo, generateExample, normalizeExamples, getSchemaFromParam, json2xml, nestExampleIfPresent } from '~/utils/schema-utils';
16
+ import { copyToClipboard, downloadResource, viewResource } from '~/utils/common-utils';
17
+ import { schemaInObjectNotation,
18
+ getTypeInfo,
19
+ generateExample,
20
+ normalizeExamples,
21
+ getSchemaFromParam,
22
+ json2xml,
23
+ nestExampleIfPresent,
24
+ anyExampleWithSummaryOrDescription } from '~/utils/schema-utils';
17
25
  import '~/components/json-tree';
18
26
  import '~/components/schema-tree';
19
27
  import '~/components/tag-input';
@@ -46,14 +54,16 @@ export default class ApiRequest extends LitElement {
46
54
  parser: { type: Object },
47
55
  accept: { type: String },
48
56
  callback: { type: String },
57
+ webhook: { type: String },
49
58
  responseMessage: { type: String, attribute: false },
50
59
  responseText: { type: String, attribute: false },
51
60
  responseHeaders: { type: String, attribute: false },
52
61
  responseStatus: { type: String, attribute: false },
53
62
  responseUrl: { type: String, attribute: false },
63
+ curlSyntax: { type: String, attribute: false },
54
64
  fillRequestFieldsWithExample: { type: String, attribute: 'fill-request-fields-with-example' },
55
- useSummaryToListExamples: { type: String, attribute: 'use-summary-to-list-example' },
56
65
  allowTry: { type: String, attribute: 'allow-try' },
66
+ showCurlBeforeTry: { type: String, attribute: 'show-curl-before-try' },
57
67
  renderStyle: { type: String, attribute: 'render-style' },
58
68
  schemaStyle: { type: String, attribute: 'schema-style' },
59
69
  activeSchemaTab: { type: String, attribute: 'active-schema-tab' },
@@ -90,7 +100,9 @@ export default class ApiRequest extends LitElement {
90
100
  PrismStyles,
91
101
  css`
92
102
  *, *:before, *:after { box-sizing: border-box; }
93
-
103
+ :where(button, input[type="checkbox"], [tabindex="0"]):focus-visible { box-shadow: var(--focus-shadow); }
104
+ :where(input[type="text"], input[type="password"], select, textarea):focus-visible { border-color: var(--primary-color); }
105
+ tag-input:focus-within { outline: 1px solid;}
94
106
  .read-mode {
95
107
  margin-top: 24px;
96
108
  }
@@ -194,70 +206,118 @@ export default class ApiRequest extends LitElement {
194
206
  ${this.callback === 'true' ? 'CALLBACK REQUEST' : 'REQUEST'}
195
207
  </div>
196
208
  <div>
197
- ${guard([this.allowTry, this.parameters, this.activeParameterSchemaTabs], () => this.inputParametersTemplate('path'))}
198
- ${guard([this.allowTry, this.parameters, this.activeParameterSchemaTabs], () => this.inputParametersTemplate('query'))}
209
+ ${guard([this.method, this.path, this.allowTry, this.parameters, this.activeParameterSchemaTabs], () => this.inputParametersTemplate('path'))}
210
+ ${guard([this.method, this.path, this.allowTry, this.parameters, this.activeParameterSchemaTabs], () => this.inputParametersTemplate('query'))}
199
211
  ${this.requestBodyTemplate()}
200
- ${guard([this.allowTry, this.parameters, this.activeParameterSchemaTabs], () => this.inputParametersTemplate('header'))}
201
- ${guard([this.allowTry, this.parameters, this.activeParameterSchemaTabs], () => this.inputParametersTemplate('cookie'))}
212
+ ${guard([this.method, this.path, this.allowTry, this.parameters, this.activeParameterSchemaTabs], () => this.inputParametersTemplate('header'))}
213
+ ${guard([this.method, this.path, this.allowTry, this.parameters, this.activeParameterSchemaTabs], () => this.inputParametersTemplate('cookie'))}
202
214
  ${this.allowTry === 'false' ? '' : html`${this.apiCallTemplate()}`}
203
215
  </div>
204
216
  </div>
205
217
  `;
206
218
  }
207
219
 
208
- updated(changedProperties) {
220
+ async updated() {
221
+ if (this.showCurlBeforeTry === 'true') {
222
+ this.applyCURLSyntax(this.shadowRoot);
223
+ }
224
+
209
225
  // In focused mode after rendering the request component, update the text-areas(which contains examples) using
210
226
  // the original values from hidden textareas
211
227
  // 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
228
+
229
+ // if (this.renderStyle === 'focused') {
230
+ // if (changedProperties.size === 1 && changedProperties.has('activeSchemaTab')) {
231
+ // // dont update example as only tabs is switched
232
+ // } else {
233
+ // this.requestUpdate();
234
+ // }
235
+ // }
236
+
237
+ if (this.webhook === 'true') {
238
+ this.allowTry = 'false';
239
+ }
240
+ }
241
+
242
+ async saveExampleState() {
212
243
  if (this.renderStyle === 'focused') {
213
- if (changedProperties.size === 1 && changedProperties.has('activeSchemaTab')) {
214
- // dont update example as only tabs is switched
215
- } else {
216
- const exampleTextAreaEls = [...this.shadowRoot.querySelectorAll('textarea[data-ptype="form-data"]')];
217
- exampleTextAreaEls.forEach((el) => {
218
- const origExampleEl = this.shadowRoot.querySelector(`textarea[data-pname='hidden-${el.dataset.pname}']`);
219
- if (origExampleEl) {
220
- el.value = origExampleEl.value;
221
- }
222
- });
223
- }
244
+ const reqBodyTextAreaEls = [...this.shadowRoot.querySelectorAll('textarea.request-body-param-user-input')];
245
+ reqBodyTextAreaEls.forEach((el) => {
246
+ el.dataset.user_example = el.value;
247
+ });
248
+ const exampleTextAreaEls = [...this.shadowRoot.querySelectorAll('textarea[data-ptype="form-data"]')];
249
+ exampleTextAreaEls.forEach((el) => {
250
+ el.dataset.user_example = el.value;
251
+ });
252
+ this.requestUpdate();
224
253
  }
225
254
  }
226
255
 
227
- /* eslint-disable indent */
256
+ async updateExamplesFromDataAttr() {
257
+ // In focused mode after rendering the request component, update the text-areas(which contains examples) using
258
+ // the original values from hidden textareas
259
+ // 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
260
+ if (this.renderStyle === 'focused') {
261
+ const reqBodyTextAreaEls = [...this.shadowRoot.querySelectorAll('textarea.request-body-param-user-input')];
262
+ reqBodyTextAreaEls.forEach((el) => {
263
+ el.value = el.dataset.user_example || el.dataset.example;
264
+ });
265
+ const exampleTextAreaEls = [...this.shadowRoot.querySelectorAll('textarea[data-ptype="form-data"]')];
266
+ exampleTextAreaEls.forEach((el) => {
267
+ el.value = el.dataset.user_example || el.dataset.example;
268
+ });
269
+ this.requestUpdate();
270
+ }
271
+ }
228
272
 
229
- exampleListTemplate(paramName, paramType, exampleList = []) {
273
+ /* eslint-disable indent */
274
+ renderExample(example, paramType, paramName) {
230
275
  return html`
231
- ${(exampleList.length > 0
232
- ? html`<span style="font-weight:bold">Example: </span>
233
- ${exampleList.map((v, i) => html`
234
- ${i === 0 ? '' : '┃'}
235
- ${paramType === 'array' ? '[' : ''}
236
- <a part="anchor anchor-param-example" class = "${this.allowTry === 'true' ? '' : 'inactive-link'}"
237
- data-example-type="${paramType === 'array' ? paramType : 'string'}"
238
- data-example = "${v.value && Array.isArray(v.value) ? (v.value?.join('~|~')) : (v.value || '')}"
239
- @click="${(e) => {
240
- const inputEl = e.target.closest('table').querySelector(`[data-pname="${paramName}"]`);
241
- if (inputEl) {
242
- if (e.target.dataset.exampleType === 'array') {
243
- inputEl.value = e.target.dataset.example.split('~|~');
244
- } else {
245
- inputEl.value = e.target.dataset.example;
246
- }
247
- }
248
- }
249
- }"
250
- >
251
- ${this.useSummaryToListExamples === 'true'
252
- ? v.description || v.summary || (v.value && Array.isArray(v.value) ? (v.value?.join(', ')) : (v.value || ''))
253
- : v.value && Array.isArray(v.value) ? (v.value?.join(', ')) : (v.value || '')
276
+ ${paramType === 'array' ? '[' : ''}
277
+ <a
278
+ part="anchor anchor-param-example"
279
+ style="display:inline-block; min-width:24px; text-align:center"
280
+ class="${this.allowTry === 'true' ? '' : 'inactive-link'}"
281
+ data-example-type="${paramType === 'array' ? paramType : 'string'}"
282
+ data-example="${example.value && Array.isArray(example.value) ? example.value?.join('~|~') : example.value || ''}"
283
+ @click="${(e) => {
284
+ const inputEl = e.target.closest('table').querySelector(`[data-pname="${paramName}"]`);
285
+ if (inputEl) {
286
+ inputEl.value = e.target.dataset.exampleType === 'array' ? e.target.dataset.example.split('~|~') : e.target.dataset.example;
254
287
  }
255
- </a>
256
- ${paramType === 'array' ? '] ' : ''}
288
+ }}"
289
+ > ${example.printableValue || example.value} </a>
290
+ ${paramType === 'array' ? '] ' : ''}
291
+ `;
292
+ }
293
+
294
+ renderShortFormatExamples(examples, paramType, paramName) {
295
+ return html`${examples.map((x, i) => html`
296
+ ${i === 0 ? '' : '┃'}
297
+ ${this.renderExample(x, paramType, paramName)}`)}`;
298
+ }
299
+
300
+ renderLongFormatExamples(exampleList, paramType, paramName) {
301
+ return html` <ul style="list-style-type: disclosure-closed;">
302
+ ${exampleList.map((v) => html`
303
+ <li>
304
+ ${this.renderExample(v, paramType, paramName)}
305
+ ${v.summary?.length > 0 ? html`<span>&lpar;${v.summary}&rpar;</span>` : ''}
306
+ ${v.description?.length > 0 ? html`<p>${unsafeHTML(marked(v.description))}</p>` : ''}
307
+ </li>
257
308
  `)}
258
- `
259
- : ''
260
- )}`;
309
+ </ul>`;
310
+ }
311
+
312
+ exampleListTemplate(paramName, paramType, exampleList = []) {
313
+ return html` ${
314
+ exampleList.length > 0
315
+ ? html`<span style="font-weight:bold">Examples: </span>
316
+ ${anyExampleWithSummaryOrDescription(exampleList)
317
+ ? this.renderLongFormatExamples(exampleList, paramType, paramName)
318
+ : this.renderShortFormatExamples(exampleList, paramType, paramName)}`
319
+ : ''
320
+ }`;
261
321
  }
262
322
 
263
323
  inputParametersTemplate(paramType) {
@@ -280,7 +340,7 @@ export default class ApiRequest extends LitElement {
280
340
  for (const param of filteredParams) {
281
341
  const [declaredParamSchema, serializeStyle, mimeTypeElem] = getSchemaFromParam(param);
282
342
  if (!declaredParamSchema) {
283
- continue; // eslint-disable-line no-continue
343
+ continue;
284
344
  }
285
345
  const paramSchema = getTypeInfo(declaredParamSchema);
286
346
  if (!paramSchema) {
@@ -305,14 +365,13 @@ export default class ApiRequest extends LitElement {
305
365
  paramAllowReserved = param.allowReserved;
306
366
  }
307
367
  }
308
-
309
368
  // openapi 3.1.0 spec based examples (which must be Object(string : { value:any, summary?: string, description?: string})
310
369
  const example = normalizeExamples(
311
370
  (param.examples
312
371
  || nestExampleIfPresent(param.example)
313
372
  || nestExampleIfPresent(mimeTypeElem?.example)
314
373
  || mimeTypeElem?.examples
315
- || paramSchema.examples
374
+ || nestExampleIfPresent(paramSchema.examples)
316
375
  || nestExampleIfPresent(paramSchema.example)
317
376
  ),
318
377
  paramSchema.type,
@@ -323,8 +382,9 @@ export default class ApiRequest extends LitElement {
323
382
  serializeStyle || 'json',
324
383
  '',
325
384
  '',
326
- !(this.schemaHideReadOnly && (this.schemaHideReadOnly.includes(this.method) || this.schemaHideReadOnly === 'true')),
327
- !(this.schemaHideWriteOnly && (this.schemaHideWriteOnly.includes(this.method) || this.schemaHideWriteOnly === 'true')),
385
+ this.callback === 'true' || this.webhook === 'true' ? true : false, // eslint-disable-line no-unneeded-ternary
386
+ this.callback === 'true' || this.webhook === 'true' ? false : true, // eslint-disable-line no-unneeded-ternary
387
+ true,
328
388
  'text',
329
389
  false,
330
390
  )[0].exampleValue;
@@ -358,9 +418,13 @@ export default class ApiRequest extends LitElement {
358
418
  data-param-serialize-style = "${paramStyle}"
359
419
  data-param-serialize-explode = "${paramExplode}"
360
420
  data-param-allow-reserved = "${paramAllowReserved}"
421
+ data-x-fill-example = "${param['x-fill-example'] || 'yes'}"
361
422
  data-array = "true"
362
423
  placeholder = "add-multiple &#x21a9;"
363
- .value = "${Array.isArray(example.exampleVal) ? example.exampleVal : example.exampleVal}"
424
+ .value="${param['x-fill-example'] === 'no'
425
+ ? []
426
+ : live(this.fillRequestFieldsWithExample === 'true' ? Array.isArray(example.exampleVal) ? example.exampleVal : [example.exampleVal] : [])
427
+ }"
364
428
  >
365
429
  </tag-input>`
366
430
  : paramSchema.type === 'object'
@@ -387,25 +451,30 @@ export default class ApiRequest extends LitElement {
387
451
  data-param-serialize-style = "${paramStyle}"
388
452
  data-param-serialize-explode = "${paramExplode}"
389
453
  data-param-allow-reserved = "${paramAllowReserved}"
454
+ data-x-fill-example = "${param['x-fill-example'] || 'yes'}"
390
455
  spellcheck = "false"
391
- .textContent = "${this.fillRequestFieldsWithExample === 'true' ? example.exampleVal : ''}"
456
+ .textContent="${param['x-fill-example'] === 'no' ? '' : live(this.fillRequestFieldsWithExample === 'true' ? example.exampleVal : '')}"
392
457
  style = "resize:vertical; width:100%; height: ${'read focused'.includes(this.renderStyle) ? '180px' : '120px'};"
458
+ @input=${(e) => {
459
+ const requestPanelEl = this.getRequestPanel(e);
460
+ this.liveCURLSyntaxUpdate(requestPanelEl);
461
+ }}
393
462
  ></textarea>
394
463
  </div>`
395
464
  : html`
396
- <div class="tab-content col">
465
+ <div class="tab-content col">
397
466
  <schema-tree
398
467
  class = 'json'
399
468
  style = 'display: block'
400
469
  .data = '${schemaAsObj}'
401
470
  schema-expand-level = "${this.schemaExpandLevel}"
402
471
  schema-description-expanded = "${this.schemaDescriptionExpanded}"
403
- allow-schema-description-expand-toggle = "${this.allowSchemaDescriptionExpandToggle}",
472
+ allow-schema-description-expand-toggle = "${this.allowSchemaDescriptionExpandToggle}"
404
473
  schema-hide-read-only = "${this.schemaHideReadOnly.includes(this.method)}"
405
474
  schema-hide-write-only = "${this.schemaHideWriteOnly.includes(this.method)}"
406
- exportparts = "btn:btn, btn-fill:btn-fill, btn-outline:btn-outline, btn-try:btn-try, btn-clear:btn-clear, btn-clear-resp:btn-clear-resp,
407
- file-input:file-input, textbox:textbox, textbox-param:textbox-param, textarea:textarea, textarea-param:textarea-param,
408
- anchor:anchor, anchor-param-example:anchor-param-example"
475
+ 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,
476
+ file-input:file-input, textbox:textbox, textbox-param:textbox-param, textarea:textarea, textarea-param:textarea-param,
477
+ anchor:anchor, anchor-param-example:anchor-param-example"
409
478
  > </schema-tree>
410
479
  </div>`
411
480
  }
@@ -421,6 +490,10 @@ export default class ApiRequest extends LitElement {
421
490
  data-x-fill-example = "${param['x-fill-example'] || 'yes'}"
422
491
  data-array="false"
423
492
  .value="${param['x-fill-example'] === 'no' ? '' : live(this.fillRequestFieldsWithExample === 'true' ? example.exampleVal : '')}"
493
+ @input=${(e) => {
494
+ const requestPanelEl = this.getRequestPanel(e);
495
+ this.liveCURLSyntaxUpdate(requestPanelEl);
496
+ }}
424
497
  />`
425
498
  }
426
499
  </td>`
@@ -469,15 +542,22 @@ export default class ApiRequest extends LitElement {
469
542
  return html`
470
543
  <div class="table-title top-gap">${title}</div>
471
544
  <div style="display:block; overflow-x:auto; max-width:100%;">
472
- <table class="m-table" style="width:100%; word-break:break-word;">
545
+ <table role="presentation" class="m-table" style="width:100%; word-break:break-word;">
473
546
  ${tableRows}
474
547
  </table>
475
548
  </div>`;
476
549
  }
477
550
 
478
- resetRequestBodySelection() {
551
+ // This method is called before navigation change in focused mode
552
+ async beforeNavigationFocusedMode() {
553
+ // this.saveExampleState();
554
+ }
555
+
556
+ // This method is called after navigation change in focused mode
557
+ async afterNavigationFocusedMode() {
479
558
  this.selectedRequestBodyType = '';
480
559
  this.selectedRequestBodyExample = '';
560
+ this.updateExamplesFromDataAttr();
481
561
  this.clearResponseData();
482
562
  }
483
563
 
@@ -489,6 +569,9 @@ export default class ApiRequest extends LitElement {
489
569
  const readOnlyExampleEl = selectEl.closest('.example-panel').querySelector('.request-body-param');
490
570
  const userInputExampleTextareaEl = selectEl.closest('.example-panel').querySelector('.request-body-param-user-input');
491
571
  userInputExampleTextareaEl.value = readOnlyExampleEl.innerText;
572
+
573
+ const requestPanelEl = this.getRequestPanel({ target: selectEl });
574
+ this.liveCURLSyntaxUpdate(requestPanelEl);
492
575
  }, 0, exampleDropdownEl);
493
576
  }
494
577
 
@@ -559,8 +642,8 @@ export default class ApiRequest extends LitElement {
559
642
  reqBody.mimeType,
560
643
  reqBody.examples,
561
644
  reqBody.example,
562
- !(this.schemaHideReadOnly && (this.schemaHideReadOnly.includes(this.method) || this.schemaHideReadOnly === 'true')),
563
- !(this.schemaHideWriteOnly && (this.schemaHideWriteOnly.includes(this.method) || this.schemaHideWriteOnly === 'true')),
645
+ this.callback === 'true' || this.webhook === 'true' ? true : false, // eslint-disable-line no-unneeded-ternary
646
+ this.callback === 'true' || this.webhook === 'true' ? false : true, // eslint-disable-line no-unneeded-ternary
564
647
  'text',
565
648
  false,
566
649
  );
@@ -604,6 +687,10 @@ export default class ApiRequest extends LitElement {
604
687
  data-example-format = "${v.exampleFormat}"
605
688
  style="width:100%; resize:vertical;"
606
689
  .textContent = "${this.fillRequestFieldsWithExample === 'true' ? (v.exampleFormat === 'text' ? v.exampleValue : JSON.stringify(v.exampleValue, null, 2)) : ''}"
690
+ @input=${(e) => {
691
+ const requestPanelEl = this.getRequestPanel(e);
692
+ this.liveCURLSyntaxUpdate(requestPanelEl);
693
+ }}
607
694
  ></textarea>
608
695
  </div>
609
696
  `)}
@@ -618,8 +705,8 @@ export default class ApiRequest extends LitElement {
618
705
  reqBody.mimeType,
619
706
  reqBody.examples,
620
707
  reqBody.example,
621
- !(this.schemaHideReadOnly && (this.schemaHideReadOnly.includes(this.method) || this.schemaHideReadOnly === 'true')),
622
- !(this.schemaHideWriteOnly && (this.schemaHideWriteOnly.includes(this.method) || this.schemaHideWriteOnly === 'true')),
708
+ this.callback === 'true' || this.webhook === 'true' ? true : false, // eslint-disable-line no-unneeded-ternary
709
+ this.callback === 'true' || this.webhook === 'true' ? false : true, // eslint-disable-line no-unneeded-ternary
623
710
  'text',
624
711
  false,
625
712
  );
@@ -649,7 +736,7 @@ export default class ApiRequest extends LitElement {
649
736
  .data = '${schemaAsObj}'
650
737
  schema-expand-level = "${this.schemaExpandLevel}"
651
738
  schema-description-expanded = "${this.schemaDescriptionExpanded}"
652
- allow-schema-description-expand-toggle = "${this.allowSchemaDescriptionExpandToggle}",
739
+ allow-schema-description-expand-toggle = "${this.allowSchemaDescriptionExpandToggle}"
653
740
  schema-hide-read-only = "${this.schemaHideReadOnly}"
654
741
  schema-hide-write-only = "${this.schemaHideWriteOnly}"
655
742
  exportparts = "schema-description:schema-description, schema-multiline-toggle:schema-multiline-toggle"
@@ -664,7 +751,7 @@ export default class ApiRequest extends LitElement {
664
751
  .data = "${schemaAsObj}"
665
752
  schema-expand-level = "${this.schemaExpandLevel}"
666
753
  schema-description-expanded = "${this.schemaDescriptionExpanded}"
667
- allow-schema-description-expand-toggle = "${this.allowSchemaDescriptionExpandToggle}",
754
+ allow-schema-description-expand-toggle = "${this.allowSchemaDescriptionExpandToggle}"
668
755
  schema-hide-read-only = "${this.schemaHideReadOnly}"
669
756
  schema-hide-write-only = "${this.schemaHideWriteOnly}"
670
757
  exportparts = "schema-description:schema-description, schema-multiline-toggle:schema-multiline-toggle"
@@ -691,10 +778,8 @@ export default class ApiRequest extends LitElement {
691
778
  <button class="tab-btn ${this.activeSchemaTab === 'example' ? 'active' : ''}" data-tab = 'example'>EXAMPLE</button>
692
779
  <button class="tab-btn ${this.activeSchemaTab !== 'example' ? 'active' : ''}" data-tab = 'schema'>SCHEMA</button>
693
780
  </div>
694
- ${this.activeSchemaTab === 'example'
695
- ? html`<div class="tab-content col"> ${reqBodyExampleHtml}</div>`
696
- : html`<div class="tab-content col"> ${reqBodySchemaHtml}</div>`
697
- }
781
+ ${html`<div class="tab-content col" style="display:${this.activeSchemaTab === 'example' ? 'block' : 'none'};"> ${reqBodyExampleHtml}</div>`}
782
+ ${html`<div class="tab-content col" style="display:${this.activeSchemaTab === 'example' ? 'none' : 'block'};"> ${reqBodySchemaHtml}</div>`}
698
783
  </div>`
699
784
  : html`
700
785
  ${reqBodyFileInputHtml}
@@ -712,8 +797,8 @@ export default class ApiRequest extends LitElement {
712
797
  'json',
713
798
  fieldSchema.examples,
714
799
  fieldSchema.example,
715
- !(this.schemaHideReadOnly && (this.schemaHideReadOnly.includes(this.method) || this.schemaHideReadOnly === 'true')),
716
- !(this.schemaHideWriteOnly && (this.schemaHideWriteOnly.includes(this.method) || this.schemaHideWriteOnly === 'true')),
800
+ this.callback === 'true' || this.webhook === 'true' ? true : false, // eslint-disable-line no-unneeded-ternary
801
+ this.callback === 'true' || this.webhook === 'true' ? false : true, // eslint-disable-line no-unneeded-ternary
717
802
  'text',
718
803
  false,
719
804
  );
@@ -755,8 +840,6 @@ export default class ApiRequest extends LitElement {
755
840
  .textContent = "${this.fillRequestFieldsWithExample === 'true' ? formdataPartExample[0].exampleValue : ''}"
756
841
  spellcheck = "false"
757
842
  ></textarea>
758
- <!-- This textarea(hidden) is to store the original example value, in focused mode on navbar change it is used to update the example text -->
759
- <textarea data-pname = "hidden-${fieldName}" data-ptype = "${mimeType.includes('form-urlencode') ? 'hidden-form-urlencode' : 'hidden-form-data'}" class="is-hidden" style="display:none">${formdataPartExample[0].exampleValue}</textarea>
760
843
  </div>`
761
844
  }
762
845
  ${html`
@@ -901,7 +984,7 @@ export default class ApiRequest extends LitElement {
901
984
  }`);
902
985
  }
903
986
  return html`
904
- <table style="width:100%;" class="m-table">
987
+ <table role="presentation" style="width:100%;" class="m-table">
905
988
  ${formDataTableRows}
906
989
  </table>
907
990
  `;
@@ -921,8 +1004,34 @@ export default class ApiRequest extends LitElement {
921
1004
  `;
922
1005
  }
923
1006
 
1007
+ curlSyntaxTemplate(display = 'flex') {
1008
+ return html`
1009
+ <div class="col m-markdown" style="flex:1; display:${display}; position:relative; max-width: 100%;">
1010
+ <button class="toolbar-btn" style = "position:absolute; top:12px; right:8px" @click='${(e) => { copyToClipboard(this.curlSyntax.replace(/\\$/, ''), e); }}' part="btn btn-fill"> Copy </button>
1011
+ <pre style="white-space:pre"><code>${unsafeHTML(Prism.highlight(this.curlSyntax.trim().replace(/\\$/, ''), Prism.languages.shell, 'shell'))}</code></pre>
1012
+ </div>
1013
+ `;
1014
+ }
1015
+
924
1016
  apiResponseTabTemplate() {
925
- const responseFormat = this.responseHeaders.includes('json') ? 'json' : (this.responseHeaders.includes('html') || this.responseHeaders.includes('xml')) ? 'html' : '';
1017
+ let responseFormat = '';
1018
+ let responseContent = '';
1019
+ if (!this.responseIsBlob) {
1020
+ if (this.responseHeaders.includes('application/x-ndjson')) {
1021
+ responseFormat = 'json';
1022
+ const prismLines = this.responseText.split('\n').map((q) => Prism.highlight(q, Prism.languages[responseFormat], responseFormat)).join('\n');
1023
+ responseContent = html`<code>${unsafeHTML(prismLines)}</code>`;
1024
+ } else if (this.responseHeaders.includes('json')) {
1025
+ responseFormat = 'json';
1026
+ responseContent = html`<code>${unsafeHTML(Prism.highlight(this.responseText, Prism.languages[responseFormat], responseFormat))}</code>`;
1027
+ } else if (this.responseHeaders.includes('html') || this.responseHeaders.includes('xml')) {
1028
+ responseFormat = 'html';
1029
+ responseContent = html`<code>${unsafeHTML(Prism.highlight(this.responseText, Prism.languages[responseFormat], responseFormat))}</code>`;
1030
+ } else {
1031
+ responseFormat = 'text';
1032
+ responseContent = html`<code>${this.responseText}</code>`;
1033
+ }
1034
+ }
926
1035
  return html`
927
1036
  <div class="row" style="font-size:var(--font-size-small); margin:5px 0">
928
1037
  <div class="response-message ${this.responseStatus}">Response Status: ${this.responseMessage}</div>
@@ -936,7 +1045,9 @@ export default class ApiRequest extends LitElement {
936
1045
  }}">
937
1046
  <button class="tab-btn ${this.activeResponseTab === 'response' ? 'active' : ''}" data-tab = 'response' > RESPONSE</button>
938
1047
  <button class="tab-btn ${this.activeResponseTab === 'headers' ? 'active' : ''}" data-tab = 'headers' > RESPONSE HEADERS</button>
939
- <button class="tab-btn ${this.activeResponseTab === 'curl' ? 'active' : ''}" data-tab = 'curl'>CURL</button>
1048
+ ${this.showCurlBeforeTry === 'true'
1049
+ ? ''
1050
+ : html`<button class="tab-btn ${this.activeResponseTab === 'curl' ? 'active' : ''}" data-tab = 'curl'>CURL</button>`}
940
1051
  </div>
941
1052
  ${this.responseIsBlob
942
1053
  ? html`
@@ -952,20 +1063,14 @@ export default class ApiRequest extends LitElement {
952
1063
  : html`
953
1064
  <div class="tab-content col m-markdown" style="flex:1; display:${this.activeResponseTab === 'response' ? 'flex' : 'none'};" >
954
1065
  <button class="toolbar-btn" style="position:absolute; top:12px; right:8px" @click='${(e) => { copyToClipboard(this.responseText, e); }}' part="btn btn-fill"> Copy </button>
955
- <pre style="white-space:pre; min-height:50px; height:400px; resize:vertical; overflow:auto">${responseFormat
956
- ? html`<code>${unsafeHTML(Prism.highlight(this.responseText, Prism.languages[responseFormat], responseFormat))}</code>`
957
- : `${this.responseText}`
958
- }</pre>
1066
+ <pre style="white-space:pre; min-height:50px; height:var(--resp-area-height, 400px); resize:vertical; overflow:auto">${responseContent}</pre>
959
1067
  </div>`
960
1068
  }
961
1069
  <div class="tab-content col m-markdown" style="flex:1; display:${this.activeResponseTab === 'headers' ? 'flex' : 'none'};" >
962
1070
  <button class="toolbar-btn" style = "position:absolute; top:12px; right:8px" @click='${(e) => { copyToClipboard(this.responseHeaders, e); }}' part="btn btn-fill"> Copy </button>
963
1071
  <pre style="white-space:pre"><code>${unsafeHTML(Prism.highlight(this.responseHeaders, Prism.languages.css, 'css'))}</code></pre>
964
1072
  </div>
965
- <div class="tab-content col m-markdown" style="flex:1; display:${this.activeResponseTab === 'curl' ? 'flex' : 'none'};">
966
- <button class="toolbar-btn" style = "position:absolute; top:12px; right:8px" @click='${(e) => { copyToClipboard(this.curlSyntax.replace(/\\$/, ''), e); }}' part="btn btn-fill"> Copy </button>
967
- <pre style="white-space:pre"><code>${unsafeHTML(Prism.highlight(this.curlSyntax.trim().replace(/\\$/, ''), Prism.languages.shell, 'shell'))}</code></pre>
968
- </div>
1073
+ ${this.showCurlBeforeTry === 'true' ? '' : this.curlSyntaxTemplate(this.activeResponseTab === 'curl' ? 'flex' : 'none')}
969
1074
  </div>`;
970
1075
  }
971
1076
 
@@ -995,7 +1100,7 @@ export default class ApiRequest extends LitElement {
995
1100
  `;
996
1101
 
997
1102
  return html`
998
- <div style="display:flex; align-items:flex-end; margin:16px 0; font-size:var(--font-size-small);">
1103
+ <div style="display:flex; align-items:flex-end; margin:16px 0; font-size:var(--font-size-small);" part="wrap-request-btn">
999
1104
  <div class="hide-in-small-screen" style="flex-direction:column; margin:0; width:calc(100% - 60px);">
1000
1105
  <div style="display:flex; flex-direction:row; align-items:center; overflow:hidden;">
1001
1106
  ${selectedServerHtml}
@@ -1030,6 +1135,9 @@ export default class ApiRequest extends LitElement {
1030
1135
  }
1031
1136
  <button class="m-btn primary thin-border" part="btn btn-try" @click="${this.onTryClick}">TRY</button>
1032
1137
  </div>
1138
+ <div class="row" style="font-size:var(--font-size-small); margin:5px 0">
1139
+ ${this.showCurlBeforeTry === 'true' ? this.curlSyntaxTemplate() : ''}
1140
+ </div>
1033
1141
  ${this.responseMessage === '' ? '' : this.apiResponseTabTemplate()}
1034
1142
  `;
1035
1143
  }
@@ -1055,27 +1163,12 @@ export default class ApiRequest extends LitElement {
1055
1163
  requestPanelInputEls.forEach((el) => { el.value = ''; });
1056
1164
  }
1057
1165
 
1058
- async onTryClick(e) {
1059
- // const me = this;
1060
- const tryBtnEl = e.target;
1166
+ buildFetchURL(requestPanelEl) {
1061
1167
  let fetchUrl;
1062
- let curlUrl;
1063
- let curl = '';
1064
- let curlHeaders = '';
1065
- let curlData = '';
1066
- let curlForm = '';
1067
- const respEl = this.closest('.expanded-req-resp-container, .req-resp-container')?.getElementsByTagName('api-response')[0];
1068
- const acceptHeader = respEl?.selectedMimeType;
1069
- const requestPanelEl = e.target.closest('.request-panel');
1070
1168
  const pathParamEls = [...requestPanelEl.querySelectorAll("[data-ptype='path']")];
1071
1169
  const queryParamEls = [...requestPanelEl.querySelectorAll("[data-ptype='query']")];
1072
1170
  const queryParamObjTypeEls = [...requestPanelEl.querySelectorAll("[data-ptype='query-object']")];
1073
- const headerParamEls = [...requestPanelEl.querySelectorAll("[data-ptype='header']")];
1074
- const requestBodyContainerEl = requestPanelEl.querySelector('.request-body-container');
1075
1171
  fetchUrl = this.path;
1076
- const fetchOptions = {
1077
- method: this.method.toUpperCase(),
1078
- };
1079
1172
  // Generate URL using Path Params
1080
1173
  pathParamEls.map((el) => {
1081
1174
  fetchUrl = fetchUrl.replace(`{${el.dataset.pname}}`, encodeURIComponent(el.value));
@@ -1159,7 +1252,7 @@ export default class ApiRequest extends LitElement {
1159
1252
  }
1160
1253
  }
1161
1254
  } catch (err) {
1162
- console.log('RapiDoc: unable to parse %s into object', el.value); // eslint-disable-line no-console
1255
+ console.error('RapiDoc: unable to parse %s into object', el.value); // eslint-disable-line no-console
1163
1256
  }
1164
1257
  if (queryParam.toString()) {
1165
1258
  urlQueryParamsMap.set(el.dataset.pname, queryParam);
@@ -1168,7 +1261,6 @@ export default class ApiRequest extends LitElement {
1168
1261
  }
1169
1262
  let urlQueryParamString = '';
1170
1263
  if (urlQueryParamsMap.size) {
1171
- urlQueryParamString = '?';
1172
1264
  urlQueryParamsMap.forEach((val, pname) => {
1173
1265
  if (queryParamsWithReservedCharsAllowed.includes(pname)) {
1174
1266
  urlQueryParamString += `${pname}=`;
@@ -1180,7 +1272,9 @@ export default class ApiRequest extends LitElement {
1180
1272
  });
1181
1273
  urlQueryParamString = urlQueryParamString.slice(0, -1);
1182
1274
  }
1183
- fetchUrl = `${fetchUrl}${urlQueryParamString}`;
1275
+ if (urlQueryParamString.length !== 0) {
1276
+ fetchUrl = `${fetchUrl}${fetchUrl.includes('?') ? '&' : '?'}${urlQueryParamString}`;
1277
+ }
1184
1278
 
1185
1279
  // Add authentication Query-Param if provided
1186
1280
  this.api_keys
@@ -1189,23 +1283,21 @@ export default class ApiRequest extends LitElement {
1189
1283
  fetchUrl = `${fetchUrl}${fetchUrl.includes('?') ? '&' : '?'}${v.name}=${encodeURIComponent(v.finalKeyValue)}`;
1190
1284
  });
1191
1285
 
1192
- // Final URL for API call
1193
1286
  fetchUrl = `${this.serverUrl.replace(/\/$/, '')}${fetchUrl}`;
1194
- if (fetchUrl.startsWith('http') === false) {
1195
- const url = new URL(fetchUrl, window.location.href);
1196
- curlUrl = url.href;
1197
- } else {
1198
- curlUrl = fetchUrl;
1199
- }
1200
- curl = `curl -X ${this.method.toUpperCase()} "${curlUrl}" \\\n`;
1287
+ return fetchUrl;
1288
+ }
1289
+
1290
+ buildFetchHeaders(requestPanelEl) {
1291
+ const respEl = this.closest('.expanded-req-resp-container, .req-resp-container')?.getElementsByTagName('api-response')[0];
1292
+ const headerParamEls = [...requestPanelEl.querySelectorAll("[data-ptype='header']")];
1293
+ const requestBodyContainerEl = requestPanelEl.querySelector('.request-body-container');
1294
+ const acceptHeader = respEl?.selectedMimeType;
1201
1295
  const reqHeaders = new Headers();
1202
1296
  if (acceptHeader) {
1203
1297
  // Uses the acceptHeader from Response panel
1204
1298
  reqHeaders.append('Accept', acceptHeader);
1205
- curlHeaders += ` -H "Accept: ${acceptHeader}" \\\n`;
1206
1299
  } else if (this.accept) {
1207
1300
  reqHeaders.append('Accept', this.accept);
1208
- curlHeaders += ` -H "Accept: ${this.accept}" \\\n`;
1209
1301
  }
1210
1302
 
1211
1303
  // Add Authentication Header if provided
@@ -1213,18 +1305,32 @@ export default class ApiRequest extends LitElement {
1213
1305
  .filter((v) => (v.in === 'header'))
1214
1306
  .forEach((v) => {
1215
1307
  reqHeaders.append(v.name, v.finalKeyValue);
1216
- curlHeaders += ` -H "${v.name}: ${v.finalKeyValue}" \\\n`;
1217
1308
  });
1218
1309
 
1219
1310
  // Add Header Params
1220
1311
  headerParamEls.map((el) => {
1221
1312
  if (el.value) {
1222
1313
  reqHeaders.append(el.dataset.pname, el.value);
1223
- curlHeaders += ` -H "${el.dataset.pname}: ${el.value}" \\\n`;
1224
1314
  }
1225
1315
  });
1226
1316
 
1227
- // Request Body Params
1317
+ if (requestBodyContainerEl) {
1318
+ const requestBodyType = requestBodyContainerEl.dataset.selectedRequestBodyType;
1319
+ // Common for all request-body
1320
+ if (!requestBodyType.includes('form-data')) {
1321
+ // For multipart/form-data dont set the content-type to allow creation of browser generated part boundaries
1322
+ reqHeaders.append('Content-Type', requestBodyType);
1323
+ }
1324
+ }
1325
+
1326
+ return reqHeaders;
1327
+ }
1328
+
1329
+ buildFetchBodyOptions(requestPanelEl) {
1330
+ const requestBodyContainerEl = requestPanelEl.querySelector('.request-body-container');
1331
+ const fetchOptions = {
1332
+ method: this.method.toUpperCase(),
1333
+ };
1228
1334
  if (requestBodyContainerEl) {
1229
1335
  const requestBodyType = requestBodyContainerEl.dataset.selectedRequestBodyType;
1230
1336
  if (requestBodyType.includes('form-urlencoded')) {
@@ -1250,7 +1356,6 @@ export default class ApiRequest extends LitElement {
1250
1356
  formUrlDynParams.append(prop, JSON.stringify(tmpObj[prop]));
1251
1357
  }
1252
1358
  fetchOptions.body = formUrlDynParams;
1253
- curlData = ` -d ${formUrlDynParams.toString()} \\\n`;
1254
1359
  }
1255
1360
  } else {
1256
1361
  // url-encoded Form Params (regular)
@@ -1269,7 +1374,6 @@ export default class ApiRequest extends LitElement {
1269
1374
  }
1270
1375
  });
1271
1376
  fetchOptions.body = formUrlParams;
1272
- curlData = ` -d ${formUrlParams.toString()} \\\n`;
1273
1377
  }
1274
1378
  } else if (requestBodyType.includes('form-data')) {
1275
1379
  const formDataParams = new FormData();
@@ -1278,15 +1382,10 @@ export default class ApiRequest extends LitElement {
1278
1382
  if (el.dataset.array === 'false') {
1279
1383
  if (el.type === 'file' && el.files[0]) {
1280
1384
  formDataParams.append(el.dataset.pname, el.files[0], el.files[0].name);
1281
- curlForm += ` -F "${el.dataset.pname}=@${el.files[0].name}" \\\n`;
1282
1385
  } else if (el.value) {
1283
1386
  formDataParams.append(el.dataset.pname, el.value);
1284
- curlForm += ` -F "${el.dataset.pname}=${el.value}" \\\n`;
1285
1387
  }
1286
1388
  } else if (el.value && Array.isArray(el.value)) {
1287
- el.value.forEach((v) => {
1288
- curlForm = `${curlForm} -F "${el.dataset.pname}[]=${v}" \\\n`;
1289
- });
1290
1389
  formDataParams.append(el.dataset.pname, el.value.join(','));
1291
1390
  }
1292
1391
  });
@@ -1295,34 +1394,27 @@ export default class ApiRequest extends LitElement {
1295
1394
  const bodyParamFileEl = requestPanelEl.querySelector('.request-body-param-file');
1296
1395
  if (bodyParamFileEl?.files[0]) {
1297
1396
  fetchOptions.body = bodyParamFileEl.files[0]; // eslint-disable-line prefer-destructuring
1298
- curlData = ` --data-binary @${bodyParamFileEl.files[0].name} \\\n`;
1299
1397
  }
1300
1398
  } else if (requestBodyType.includes('json') || requestBodyType.includes('xml') || requestBodyType.includes('text')) {
1301
1399
  const exampleTextAreaEl = requestPanelEl.querySelector('.request-body-param-user-input');
1302
1400
  if (exampleTextAreaEl?.value) {
1303
1401
  fetchOptions.body = exampleTextAreaEl.value;
1304
- if (requestBodyType.includes('json')) {
1305
- try {
1306
- curlData = ` -d '${JSON.stringify(JSON.parse(exampleTextAreaEl.value))}' \\\n`;
1307
- } catch (err) {
1308
- // Ignore.
1309
- }
1310
- }
1311
- if (!curlData) {
1312
- curlData = ` -d '${exampleTextAreaEl.value.replace(/'/g, '\'"\'"\'')}' \\\n`;
1313
- }
1314
1402
  }
1315
1403
  }
1316
- // Common for all request-body
1317
- if (!requestBodyType.includes('form-data')) {
1318
- // For multipart/form-data dont set the content-type to allow creation of browser generated part boundaries
1319
- reqHeaders.append('Content-Type', requestBodyType);
1320
- }
1321
- curlHeaders += ` -H "Content-Type: ${requestBodyType}" \\\n`;
1322
1404
  }
1405
+
1406
+ return fetchOptions;
1407
+ }
1408
+
1409
+ async onTryClick(e) {
1410
+ const tryBtnEl = e.target;
1411
+ const requestPanelEl = tryBtnEl.closest('.request-panel');
1412
+ const fetchUrl = this.buildFetchURL(requestPanelEl);
1413
+ const fetchOptions = this.buildFetchBodyOptions(requestPanelEl);
1414
+ const reqHeaders = this.buildFetchHeaders(requestPanelEl);
1323
1415
  this.responseUrl = '';
1324
1416
  this.responseHeaders = [];
1325
- this.curlSyntax = '';
1417
+ this.curlSyntax = this.generateCURLSyntax(fetchUrl, reqHeaders, fetchOptions, requestPanelEl);
1326
1418
  this.responseStatus = 'success';
1327
1419
  this.responseIsBlob = false;
1328
1420
 
@@ -1331,22 +1423,28 @@ export default class ApiRequest extends LitElement {
1331
1423
  URL.revokeObjectURL(this.responseBlobUrl);
1332
1424
  this.responseBlobUrl = '';
1333
1425
  }
1334
- this.curlSyntax = `${curl}${curlHeaders}${curlData}${curlForm}`;
1335
1426
  if (this.fetchCredentials) {
1336
1427
  fetchOptions.credentials = this.fetchCredentials;
1337
1428
  }
1338
1429
  const controller = new AbortController();
1339
1430
  const { signal } = controller;
1340
1431
  fetchOptions.headers = reqHeaders;
1341
- const fetchRequest = new Request(fetchUrl, fetchOptions);
1432
+ const tempRequest = { url: fetchUrl, ...fetchOptions };
1342
1433
  this.dispatchEvent(new CustomEvent('before-try', {
1343
1434
  bubbles: true,
1344
1435
  composed: true,
1345
1436
  detail: {
1346
- request: fetchRequest,
1437
+ request: tempRequest,
1347
1438
  controller,
1348
1439
  },
1349
1440
  }));
1441
+ const updatedFetchOptions = {
1442
+ method: tempRequest.method,
1443
+ headers: tempRequest.headers,
1444
+ credentials: tempRequest.credentials,
1445
+ body: tempRequest.body,
1446
+ };
1447
+ const fetchRequest = new Request(tempRequest.url, updatedFetchOptions);
1350
1448
 
1351
1449
  let fetchResponse;
1352
1450
  let responseClone;
@@ -1355,6 +1453,9 @@ export default class ApiRequest extends LitElement {
1355
1453
  let respJson;
1356
1454
  let respText;
1357
1455
  tryBtnEl.disabled = true;
1456
+ this.responseText = '⌛';
1457
+ this.responseMessage = '';
1458
+ this.requestUpdate();
1358
1459
  const startTime = performance.now();
1359
1460
  fetchResponse = await fetch(fetchRequest, { signal });
1360
1461
  const endTime = performance.now();
@@ -1372,7 +1473,9 @@ export default class ApiRequest extends LitElement {
1372
1473
  if (respEmpty) {
1373
1474
  this.responseText = '';
1374
1475
  } else if (contentType) {
1375
- if (contentType.includes('json')) {
1476
+ if (contentType === 'application/x-ndjson') {
1477
+ this.responseText = await fetchResponse.text();
1478
+ } else if (contentType.includes('json')) {
1376
1479
  if ((/charset=[^"']+/).test(contentType)) {
1377
1480
  const encoding = contentType.split('charset=')[1];
1378
1481
  const buffer = await fetchResponse.arrayBuffer();
@@ -1401,9 +1504,10 @@ export default class ApiRequest extends LitElement {
1401
1504
  } else {
1402
1505
  respText = await fetchResponse.text();
1403
1506
  if (contentType.includes('xml')) {
1404
- this.responseText = prettyXml(respText);
1507
+ this.responseText = formatXml(respText, { textNodesOnSameLine: true, indentor: ' ' });
1508
+ } else {
1509
+ this.responseText = respText;
1405
1510
  }
1406
- this.responseText = respText;
1407
1511
  }
1408
1512
  if (this.responseIsBlob) {
1409
1513
  const contentDisposition = fetchResponse.headers.get('content-disposition');
@@ -1438,6 +1542,7 @@ export default class ApiRequest extends LitElement {
1438
1542
  },
1439
1543
  }));
1440
1544
  this.responseMessage = 'Request Aborted';
1545
+ this.responseText = 'Request Aborted';
1441
1546
  } else {
1442
1547
  this.dispatchEvent(new CustomEvent('after-try', {
1443
1548
  bubbles: true,
@@ -1453,6 +1558,90 @@ export default class ApiRequest extends LitElement {
1453
1558
  this.requestUpdate();
1454
1559
  }
1455
1560
 
1561
+ liveCURLSyntaxUpdate(requestPanelEl) {
1562
+ this.applyCURLSyntax(requestPanelEl);
1563
+ this.requestUpdate();
1564
+ }
1565
+
1566
+ onGenerateCURLClick(e) {
1567
+ const requestPanelEl = this.getRequestPanel(e);
1568
+ this.applyCURLSyntax(requestPanelEl);
1569
+ }
1570
+
1571
+ getRequestPanel(e) {
1572
+ return e.target.closest('.request-panel');
1573
+ }
1574
+
1575
+ applyCURLSyntax(requestPanelEl) {
1576
+ const fetchUrl = this.buildFetchURL(requestPanelEl);
1577
+ const fetchOptions = this.buildFetchBodyOptions(requestPanelEl);
1578
+ const fetchHeaders = this.buildFetchHeaders(requestPanelEl);
1579
+
1580
+ this.curlSyntax = this.generateCURLSyntax(fetchUrl, fetchHeaders, fetchOptions, requestPanelEl);
1581
+ }
1582
+
1583
+ generateCURLSyntax(fetchUrl, fetchHeaders, fetchOptions, requestPanelEl) {
1584
+ let curlUrl;
1585
+ let curl = '';
1586
+ let curlHeaders = '';
1587
+ let curlData = '';
1588
+ let curlForm = '';
1589
+ const requestBodyContainerEl = requestPanelEl.querySelector('.request-body-container');
1590
+
1591
+ if (fetchUrl.startsWith('http') === false) {
1592
+ const url = new URL(fetchUrl, window.location.href);
1593
+ curlUrl = url.href;
1594
+ } else {
1595
+ curlUrl = fetchUrl;
1596
+ }
1597
+
1598
+ curl = `curl -X ${this.method.toUpperCase()} "${curlUrl}" \\\n`;
1599
+
1600
+ curlHeaders = Array.from(fetchHeaders).map(([key, value]) => ` -H "${key}: ${value}"`).join('\\\n');
1601
+ if (curlHeaders) {
1602
+ curlHeaders = `${curlHeaders} \\\n`;
1603
+ }
1604
+ if (fetchOptions.body instanceof URLSearchParams) {
1605
+ curlData = ` -d ${fetchOptions.body.toString()} \\\n`;
1606
+ } else if (fetchOptions.body instanceof File) {
1607
+ curlData = ` --data-binary @${fetchOptions.body.name} \\\n`;
1608
+ } else if (fetchOptions.body instanceof FormData) {
1609
+ curlForm = Array.from(fetchOptions.body).reduce((aggregator, [key, value]) => {
1610
+ if (value instanceof File) {
1611
+ return [...aggregator, ` -F "${key}=@${value.name}"`];
1612
+ }
1613
+
1614
+ const multiple = value.match(/([^,],)/gm);
1615
+
1616
+ if (multiple) {
1617
+ const multipleResults = multiple.map((one) => `-F "${key}[]=${one}"`);
1618
+
1619
+ return [...aggregator, ...multipleResults];
1620
+ }
1621
+
1622
+ return [...aggregator, ` -F "${key}=${value}"`];
1623
+ }, []).join('\\\n');
1624
+ } else if (requestBodyContainerEl && requestBodyContainerEl.dataset.selectedRequestBodyType) {
1625
+ const requestBodyType = requestBodyContainerEl.dataset.selectedRequestBodyType;
1626
+ const exampleTextAreaEl = requestPanelEl.querySelector('.request-body-param-user-input');
1627
+ if (exampleTextAreaEl?.value) {
1628
+ fetchOptions.body = exampleTextAreaEl.value;
1629
+ if (requestBodyType.includes('json')) {
1630
+ try {
1631
+ curlData = ` -d '${JSON.stringify(JSON.parse(exampleTextAreaEl.value))}' \\\n`;
1632
+ } catch (err) {
1633
+ // Ignore.
1634
+ }
1635
+ }
1636
+ if (!curlData) {
1637
+ curlData = ` -d '${exampleTextAreaEl.value.replace(/'/g, '\'"\'"\'')}' \\\n`;
1638
+ }
1639
+ }
1640
+ }
1641
+
1642
+ return `${curl}${curlHeaders}${curlData}${curlForm}`;
1643
+ }
1644
+
1456
1645
  onAddRemoveFileInput(e, pname, ptype) {
1457
1646
  if (e.target.tagName.toLowerCase() !== 'button') {
1458
1647
  return;
@@ -1508,7 +1697,8 @@ export default class ApiRequest extends LitElement {
1508
1697
  }
1509
1698
 
1510
1699
  disconnectedCallback() {
1511
- // Cleanup ObjectURL forthe blob data if this component created one
1700
+ this.curlSyntax = '';
1701
+ // Cleanup ObjectURL for the blob data if this component created one
1512
1702
  if (this.responseBlobUrl) {
1513
1703
  URL.revokeObjectURL(this.responseBlobUrl);
1514
1704
  this.responseBlobUrl = '';