rapidoc 9.3.3 → 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 (43) hide show
  1. package/.github/workflow/words.yaml +22 -0
  2. package/dist/rapidoc-min.js +815 -709
  3. package/dist/rapidoc-min.js.LICENSE.txt +8 -7
  4. package/dist/rapidoc-min.js.LICENSE.txt.gz +0 -0
  5. package/dist/rapidoc-min.js.gz +0 -0
  6. package/dist/rapidoc-min.js.map +1 -1
  7. package/dist/rapidoc-min.js.map.gz +0 -0
  8. package/dist/rapidoc.js +17680 -31489
  9. package/dist/report.html +5 -4
  10. package/package.json +18 -18
  11. package/src/components/api-request.js +218 -112
  12. package/src/components/api-response.js +11 -10
  13. package/src/components/json-tree.js +17 -12
  14. package/src/components/schema-table.js +47 -39
  15. package/src/components/schema-tree.js +55 -52
  16. package/src/components/tag-input.js +7 -4
  17. package/src/json-schema-viewer.js +1 -1
  18. package/src/rapidoc-mini.js +1 -1
  19. package/src/rapidoc.js +98 -30
  20. package/src/styles/endpoint-styles.js +1 -1
  21. package/src/styles/info-styles.js +2 -1
  22. package/src/styles/input-styles.js +2 -16
  23. package/src/styles/nav-styles.js +57 -10
  24. package/src/styles/schema-styles.js +27 -15
  25. package/src/styles/tab-styles.js +1 -0
  26. package/src/templates/callback-template.js +3 -3
  27. package/src/templates/code-samples-template.js +1 -0
  28. package/src/templates/components-template.js +3 -5
  29. package/src/templates/endpoint-template.js +38 -25
  30. package/src/templates/expanded-endpoint-template.js +57 -8
  31. package/src/templates/focused-endpoint-template.js +4 -1
  32. package/src/templates/header-template.js +2 -2
  33. package/src/templates/json-schema-viewer-template.js +2 -1
  34. package/src/templates/main-body-template.js +19 -18
  35. package/src/templates/navbar-template.js +98 -81
  36. package/src/templates/overview-template.js +1 -1
  37. package/src/templates/security-scheme-template.js +48 -25
  38. package/src/templates/server-template.js +1 -1
  39. package/src/utils/common-utils.js +1 -1
  40. package/src/utils/schema-utils.js +161 -63
  41. package/src/utils/spec-parser.js +1 -0
  42. package/src/utils/theme.js +9 -3
  43. package/webpack.config.js +2 -1
@@ -60,8 +60,10 @@ export default class ApiRequest extends LitElement {
60
60
  responseHeaders: { type: String, attribute: false },
61
61
  responseStatus: { type: String, attribute: false },
62
62
  responseUrl: { type: String, attribute: false },
63
+ curlSyntax: { type: String, attribute: false },
63
64
  fillRequestFieldsWithExample: { type: String, attribute: 'fill-request-fields-with-example' },
64
65
  allowTry: { type: String, attribute: 'allow-try' },
66
+ showCurlBeforeTry: { type: String, attribute: 'show-curl-before-try' },
65
67
  renderStyle: { type: String, attribute: 'render-style' },
66
68
  schemaStyle: { type: String, attribute: 'schema-style' },
67
69
  activeSchemaTab: { type: String, attribute: 'active-schema-tab' },
@@ -98,7 +100,9 @@ export default class ApiRequest extends LitElement {
98
100
  PrismStyles,
99
101
  css`
100
102
  *, *:before, *:after { box-sizing: border-box; }
101
-
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;}
102
106
  .read-mode {
103
107
  margin-top: 24px;
104
108
  }
@@ -213,20 +217,27 @@ export default class ApiRequest extends LitElement {
213
217
  `;
214
218
  }
215
219
 
216
- /*
217
- async updated(changedProperties) {
220
+ async updated() {
221
+ if (this.showCurlBeforeTry === 'true') {
222
+ this.applyCURLSyntax(this.shadowRoot);
223
+ }
224
+
218
225
  // In focused mode after rendering the request component, update the text-areas(which contains examples) using
219
226
  // the original values from hidden textareas
220
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
221
- if (this.renderStyle === 'focused') {
222
- if (changedProperties.size === 1 && changedProperties.has('activeSchemaTab')) {
223
- // dont update example as only tabs is switched
224
- } else {
225
- this.requestUpdate();
226
- }
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';
227
239
  }
228
240
  }
229
- */
230
241
 
231
242
  async saveExampleState() {
232
243
  if (this.renderStyle === 'focused') {
@@ -259,28 +270,23 @@ export default class ApiRequest extends LitElement {
259
270
  }
260
271
  }
261
272
 
273
+ /* eslint-disable indent */
262
274
  renderExample(example, paramType, paramName) {
263
275
  return html`
264
276
  ${paramType === 'array' ? '[' : ''}
265
277
  <a
266
278
  part="anchor anchor-param-example"
279
+ style="display:inline-block; min-width:24px; text-align:center"
267
280
  class="${this.allowTry === 'true' ? '' : 'inactive-link'}"
268
281
  data-example-type="${paramType === 'array' ? paramType : 'string'}"
269
282
  data-example="${example.value && Array.isArray(example.value) ? example.value?.join('~|~') : example.value || ''}"
270
283
  @click="${(e) => {
271
- const inputEl = e.target.closest('table').querySelector(`[data-pname="${paramName}"]`);
272
- if (inputEl) {
273
- if (e.target.dataset.exampleType === 'array') {
274
- inputEl.value = e.target.dataset.example.split('~|~');
275
- } else {
276
- inputEl.value = e.target.dataset.example;
277
- }
278
- }
279
- }
280
- }"
281
- >
282
- ${example.value && Array.isArray(example.value) ? example.value?.join(', ') : example.value || '∅'}
283
- </a>
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;
287
+ }
288
+ }}"
289
+ > ${example.printableValue || example.value} </a>
284
290
  ${paramType === 'array' ? '] ' : ''}
285
291
  `;
286
292
  }
@@ -303,17 +309,15 @@ export default class ApiRequest extends LitElement {
303
309
  </ul>`;
304
310
  }
305
311
 
306
- /* eslint-disable indent */
307
-
308
312
  exampleListTemplate(paramName, paramType, exampleList = []) {
309
- return html` ${exampleList.length > 0
310
- ? html`<span style="font-weight:bold">Examples: </span>
311
- ${
312
- anyExampleWithSummaryOrDescription(exampleList)
313
+ return html` ${
314
+ exampleList.length > 0
315
+ ? html`<span style="font-weight:bold">Examples: </span>
316
+ ${anyExampleWithSummaryOrDescription(exampleList)
313
317
  ? this.renderLongFormatExamples(exampleList, paramType, paramName)
314
- : this.renderShortFormatExamples(exampleList, paramType, paramName)
315
- }`
316
- : ''}`;
318
+ : this.renderShortFormatExamples(exampleList, paramType, paramName)}`
319
+ : ''
320
+ }`;
317
321
  }
318
322
 
319
323
  inputParametersTemplate(paramType) {
@@ -336,7 +340,7 @@ export default class ApiRequest extends LitElement {
336
340
  for (const param of filteredParams) {
337
341
  const [declaredParamSchema, serializeStyle, mimeTypeElem] = getSchemaFromParam(param);
338
342
  if (!declaredParamSchema) {
339
- continue; // eslint-disable-line no-continue
343
+ continue;
340
344
  }
341
345
  const paramSchema = getTypeInfo(declaredParamSchema);
342
346
  if (!paramSchema) {
@@ -367,7 +371,7 @@ export default class ApiRequest extends LitElement {
367
371
  || nestExampleIfPresent(param.example)
368
372
  || nestExampleIfPresent(mimeTypeElem?.example)
369
373
  || mimeTypeElem?.examples
370
- || paramSchema.examples
374
+ || nestExampleIfPresent(paramSchema.examples)
371
375
  || nestExampleIfPresent(paramSchema.example)
372
376
  ),
373
377
  paramSchema.type,
@@ -451,6 +455,10 @@ export default class ApiRequest extends LitElement {
451
455
  spellcheck = "false"
452
456
  .textContent="${param['x-fill-example'] === 'no' ? '' : live(this.fillRequestFieldsWithExample === 'true' ? example.exampleVal : '')}"
453
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
+ }}
454
462
  ></textarea>
455
463
  </div>`
456
464
  : html`
@@ -461,12 +469,12 @@ export default class ApiRequest extends LitElement {
461
469
  .data = '${schemaAsObj}'
462
470
  schema-expand-level = "${this.schemaExpandLevel}"
463
471
  schema-description-expanded = "${this.schemaDescriptionExpanded}"
464
- allow-schema-description-expand-toggle = "${this.allowSchemaDescriptionExpandToggle}",
472
+ allow-schema-description-expand-toggle = "${this.allowSchemaDescriptionExpandToggle}"
465
473
  schema-hide-read-only = "${this.schemaHideReadOnly.includes(this.method)}"
466
474
  schema-hide-write-only = "${this.schemaHideWriteOnly.includes(this.method)}"
467
- 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,
468
- file-input:file-input, textbox:textbox, textbox-param:textbox-param, textarea:textarea, textarea-param:textarea-param,
469
- 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"
470
478
  > </schema-tree>
471
479
  </div>`
472
480
  }
@@ -482,6 +490,10 @@ export default class ApiRequest extends LitElement {
482
490
  data-x-fill-example = "${param['x-fill-example'] || 'yes'}"
483
491
  data-array="false"
484
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
+ }}
485
497
  />`
486
498
  }
487
499
  </td>`
@@ -530,18 +542,18 @@ export default class ApiRequest extends LitElement {
530
542
  return html`
531
543
  <div class="table-title top-gap">${title}</div>
532
544
  <div style="display:block; overflow-x:auto; max-width:100%;">
533
- <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;">
534
546
  ${tableRows}
535
547
  </table>
536
548
  </div>`;
537
549
  }
538
550
 
539
- // This method is called before navigation change in focusd mode
540
- async beforerNavigationFocusedMode() {
551
+ // This method is called before navigation change in focused mode
552
+ async beforeNavigationFocusedMode() {
541
553
  // this.saveExampleState();
542
554
  }
543
555
 
544
- // This method is called after navigation change in focusd mode
556
+ // This method is called after navigation change in focused mode
545
557
  async afterNavigationFocusedMode() {
546
558
  this.selectedRequestBodyType = '';
547
559
  this.selectedRequestBodyExample = '';
@@ -557,6 +569,9 @@ export default class ApiRequest extends LitElement {
557
569
  const readOnlyExampleEl = selectEl.closest('.example-panel').querySelector('.request-body-param');
558
570
  const userInputExampleTextareaEl = selectEl.closest('.example-panel').querySelector('.request-body-param-user-input');
559
571
  userInputExampleTextareaEl.value = readOnlyExampleEl.innerText;
572
+
573
+ const requestPanelEl = this.getRequestPanel({ target: selectEl });
574
+ this.liveCURLSyntaxUpdate(requestPanelEl);
560
575
  }, 0, exampleDropdownEl);
561
576
  }
562
577
 
@@ -672,6 +687,10 @@ export default class ApiRequest extends LitElement {
672
687
  data-example-format = "${v.exampleFormat}"
673
688
  style="width:100%; resize:vertical;"
674
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
+ }}
675
694
  ></textarea>
676
695
  </div>
677
696
  `)}
@@ -717,7 +736,7 @@ export default class ApiRequest extends LitElement {
717
736
  .data = '${schemaAsObj}'
718
737
  schema-expand-level = "${this.schemaExpandLevel}"
719
738
  schema-description-expanded = "${this.schemaDescriptionExpanded}"
720
- allow-schema-description-expand-toggle = "${this.allowSchemaDescriptionExpandToggle}",
739
+ allow-schema-description-expand-toggle = "${this.allowSchemaDescriptionExpandToggle}"
721
740
  schema-hide-read-only = "${this.schemaHideReadOnly}"
722
741
  schema-hide-write-only = "${this.schemaHideWriteOnly}"
723
742
  exportparts = "schema-description:schema-description, schema-multiline-toggle:schema-multiline-toggle"
@@ -732,7 +751,7 @@ export default class ApiRequest extends LitElement {
732
751
  .data = "${schemaAsObj}"
733
752
  schema-expand-level = "${this.schemaExpandLevel}"
734
753
  schema-description-expanded = "${this.schemaDescriptionExpanded}"
735
- allow-schema-description-expand-toggle = "${this.allowSchemaDescriptionExpandToggle}",
754
+ allow-schema-description-expand-toggle = "${this.allowSchemaDescriptionExpandToggle}"
736
755
  schema-hide-read-only = "${this.schemaHideReadOnly}"
737
756
  schema-hide-write-only = "${this.schemaHideWriteOnly}"
738
757
  exportparts = "schema-description:schema-description, schema-multiline-toggle:schema-multiline-toggle"
@@ -965,7 +984,7 @@ export default class ApiRequest extends LitElement {
965
984
  }`);
966
985
  }
967
986
  return html`
968
- <table style="width:100%;" class="m-table">
987
+ <table role="presentation" style="width:100%;" class="m-table">
969
988
  ${formDataTableRows}
970
989
  </table>
971
990
  `;
@@ -985,6 +1004,15 @@ export default class ApiRequest extends LitElement {
985
1004
  `;
986
1005
  }
987
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
+
988
1016
  apiResponseTabTemplate() {
989
1017
  let responseFormat = '';
990
1018
  let responseContent = '';
@@ -1017,7 +1045,9 @@ export default class ApiRequest extends LitElement {
1017
1045
  }}">
1018
1046
  <button class="tab-btn ${this.activeResponseTab === 'response' ? 'active' : ''}" data-tab = 'response' > RESPONSE</button>
1019
1047
  <button class="tab-btn ${this.activeResponseTab === 'headers' ? 'active' : ''}" data-tab = 'headers' > RESPONSE HEADERS</button>
1020
- <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>`}
1021
1051
  </div>
1022
1052
  ${this.responseIsBlob
1023
1053
  ? html`
@@ -1040,10 +1070,7 @@ export default class ApiRequest extends LitElement {
1040
1070
  <button class="toolbar-btn" style = "position:absolute; top:12px; right:8px" @click='${(e) => { copyToClipboard(this.responseHeaders, e); }}' part="btn btn-fill"> Copy </button>
1041
1071
  <pre style="white-space:pre"><code>${unsafeHTML(Prism.highlight(this.responseHeaders, Prism.languages.css, 'css'))}</code></pre>
1042
1072
  </div>
1043
- <div class="tab-content col m-markdown" style="flex:1; display:${this.activeResponseTab === 'curl' ? 'flex' : 'none'};">
1044
- <button class="toolbar-btn" style = "position:absolute; top:12px; right:8px" @click='${(e) => { copyToClipboard(this.curlSyntax.replace(/\\$/, ''), e); }}' part="btn btn-fill"> Copy </button>
1045
- <pre style="white-space:pre"><code>${unsafeHTML(Prism.highlight(this.curlSyntax.trim().replace(/\\$/, ''), Prism.languages.shell, 'shell'))}</code></pre>
1046
- </div>
1073
+ ${this.showCurlBeforeTry === 'true' ? '' : this.curlSyntaxTemplate(this.activeResponseTab === 'curl' ? 'flex' : 'none')}
1047
1074
  </div>`;
1048
1075
  }
1049
1076
 
@@ -1073,7 +1100,7 @@ export default class ApiRequest extends LitElement {
1073
1100
  `;
1074
1101
 
1075
1102
  return html`
1076
- <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">
1077
1104
  <div class="hide-in-small-screen" style="flex-direction:column; margin:0; width:calc(100% - 60px);">
1078
1105
  <div style="display:flex; flex-direction:row; align-items:center; overflow:hidden;">
1079
1106
  ${selectedServerHtml}
@@ -1108,6 +1135,9 @@ export default class ApiRequest extends LitElement {
1108
1135
  }
1109
1136
  <button class="m-btn primary thin-border" part="btn btn-try" @click="${this.onTryClick}">TRY</button>
1110
1137
  </div>
1138
+ <div class="row" style="font-size:var(--font-size-small); margin:5px 0">
1139
+ ${this.showCurlBeforeTry === 'true' ? this.curlSyntaxTemplate() : ''}
1140
+ </div>
1111
1141
  ${this.responseMessage === '' ? '' : this.apiResponseTabTemplate()}
1112
1142
  `;
1113
1143
  }
@@ -1133,27 +1163,12 @@ export default class ApiRequest extends LitElement {
1133
1163
  requestPanelInputEls.forEach((el) => { el.value = ''; });
1134
1164
  }
1135
1165
 
1136
- async onTryClick(e) {
1137
- // const me = this;
1138
- const tryBtnEl = e.target;
1166
+ buildFetchURL(requestPanelEl) {
1139
1167
  let fetchUrl;
1140
- let curlUrl;
1141
- let curl = '';
1142
- let curlHeaders = '';
1143
- let curlData = '';
1144
- let curlForm = '';
1145
- const respEl = this.closest('.expanded-req-resp-container, .req-resp-container')?.getElementsByTagName('api-response')[0];
1146
- const acceptHeader = respEl?.selectedMimeType;
1147
- const requestPanelEl = e.target.closest('.request-panel');
1148
1168
  const pathParamEls = [...requestPanelEl.querySelectorAll("[data-ptype='path']")];
1149
1169
  const queryParamEls = [...requestPanelEl.querySelectorAll("[data-ptype='query']")];
1150
1170
  const queryParamObjTypeEls = [...requestPanelEl.querySelectorAll("[data-ptype='query-object']")];
1151
- const headerParamEls = [...requestPanelEl.querySelectorAll("[data-ptype='header']")];
1152
- const requestBodyContainerEl = requestPanelEl.querySelector('.request-body-container');
1153
1171
  fetchUrl = this.path;
1154
- const fetchOptions = {
1155
- method: this.method.toUpperCase(),
1156
- };
1157
1172
  // Generate URL using Path Params
1158
1173
  pathParamEls.map((el) => {
1159
1174
  fetchUrl = fetchUrl.replace(`{${el.dataset.pname}}`, encodeURIComponent(el.value));
@@ -1237,7 +1252,7 @@ export default class ApiRequest extends LitElement {
1237
1252
  }
1238
1253
  }
1239
1254
  } catch (err) {
1240
- 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
1241
1256
  }
1242
1257
  if (queryParam.toString()) {
1243
1258
  urlQueryParamsMap.set(el.dataset.pname, queryParam);
@@ -1246,7 +1261,6 @@ export default class ApiRequest extends LitElement {
1246
1261
  }
1247
1262
  let urlQueryParamString = '';
1248
1263
  if (urlQueryParamsMap.size) {
1249
- urlQueryParamString = '?';
1250
1264
  urlQueryParamsMap.forEach((val, pname) => {
1251
1265
  if (queryParamsWithReservedCharsAllowed.includes(pname)) {
1252
1266
  urlQueryParamString += `${pname}=`;
@@ -1258,7 +1272,9 @@ export default class ApiRequest extends LitElement {
1258
1272
  });
1259
1273
  urlQueryParamString = urlQueryParamString.slice(0, -1);
1260
1274
  }
1261
- fetchUrl = `${fetchUrl}${urlQueryParamString}`;
1275
+ if (urlQueryParamString.length !== 0) {
1276
+ fetchUrl = `${fetchUrl}${fetchUrl.includes('?') ? '&' : '?'}${urlQueryParamString}`;
1277
+ }
1262
1278
 
1263
1279
  // Add authentication Query-Param if provided
1264
1280
  this.api_keys
@@ -1267,23 +1283,21 @@ export default class ApiRequest extends LitElement {
1267
1283
  fetchUrl = `${fetchUrl}${fetchUrl.includes('?') ? '&' : '?'}${v.name}=${encodeURIComponent(v.finalKeyValue)}`;
1268
1284
  });
1269
1285
 
1270
- // Final URL for API call
1271
1286
  fetchUrl = `${this.serverUrl.replace(/\/$/, '')}${fetchUrl}`;
1272
- if (fetchUrl.startsWith('http') === false) {
1273
- const url = new URL(fetchUrl, window.location.href);
1274
- curlUrl = url.href;
1275
- } else {
1276
- curlUrl = fetchUrl;
1277
- }
1278
- 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;
1279
1295
  const reqHeaders = new Headers();
1280
1296
  if (acceptHeader) {
1281
1297
  // Uses the acceptHeader from Response panel
1282
1298
  reqHeaders.append('Accept', acceptHeader);
1283
- curlHeaders += ` -H "Accept: ${acceptHeader}" \\\n`;
1284
1299
  } else if (this.accept) {
1285
1300
  reqHeaders.append('Accept', this.accept);
1286
- curlHeaders += ` -H "Accept: ${this.accept}" \\\n`;
1287
1301
  }
1288
1302
 
1289
1303
  // Add Authentication Header if provided
@@ -1291,18 +1305,32 @@ export default class ApiRequest extends LitElement {
1291
1305
  .filter((v) => (v.in === 'header'))
1292
1306
  .forEach((v) => {
1293
1307
  reqHeaders.append(v.name, v.finalKeyValue);
1294
- curlHeaders += ` -H "${v.name}: ${v.finalKeyValue}" \\\n`;
1295
1308
  });
1296
1309
 
1297
1310
  // Add Header Params
1298
1311
  headerParamEls.map((el) => {
1299
1312
  if (el.value) {
1300
1313
  reqHeaders.append(el.dataset.pname, el.value);
1301
- curlHeaders += ` -H "${el.dataset.pname}: ${el.value}" \\\n`;
1302
1314
  }
1303
1315
  });
1304
1316
 
1305
- // 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
+ };
1306
1334
  if (requestBodyContainerEl) {
1307
1335
  const requestBodyType = requestBodyContainerEl.dataset.selectedRequestBodyType;
1308
1336
  if (requestBodyType.includes('form-urlencoded')) {
@@ -1328,7 +1356,6 @@ export default class ApiRequest extends LitElement {
1328
1356
  formUrlDynParams.append(prop, JSON.stringify(tmpObj[prop]));
1329
1357
  }
1330
1358
  fetchOptions.body = formUrlDynParams;
1331
- curlData = ` -d ${formUrlDynParams.toString()} \\\n`;
1332
1359
  }
1333
1360
  } else {
1334
1361
  // url-encoded Form Params (regular)
@@ -1347,7 +1374,6 @@ export default class ApiRequest extends LitElement {
1347
1374
  }
1348
1375
  });
1349
1376
  fetchOptions.body = formUrlParams;
1350
- curlData = ` -d ${formUrlParams.toString()} \\\n`;
1351
1377
  }
1352
1378
  } else if (requestBodyType.includes('form-data')) {
1353
1379
  const formDataParams = new FormData();
@@ -1356,15 +1382,10 @@ export default class ApiRequest extends LitElement {
1356
1382
  if (el.dataset.array === 'false') {
1357
1383
  if (el.type === 'file' && el.files[0]) {
1358
1384
  formDataParams.append(el.dataset.pname, el.files[0], el.files[0].name);
1359
- curlForm += ` -F "${el.dataset.pname}=@${el.files[0].name}" \\\n`;
1360
1385
  } else if (el.value) {
1361
1386
  formDataParams.append(el.dataset.pname, el.value);
1362
- curlForm += ` -F "${el.dataset.pname}=${el.value}" \\\n`;
1363
1387
  }
1364
1388
  } else if (el.value && Array.isArray(el.value)) {
1365
- el.value.forEach((v) => {
1366
- curlForm = `${curlForm} -F "${el.dataset.pname}[]=${v}" \\\n`;
1367
- });
1368
1389
  formDataParams.append(el.dataset.pname, el.value.join(','));
1369
1390
  }
1370
1391
  });
@@ -1373,34 +1394,27 @@ export default class ApiRequest extends LitElement {
1373
1394
  const bodyParamFileEl = requestPanelEl.querySelector('.request-body-param-file');
1374
1395
  if (bodyParamFileEl?.files[0]) {
1375
1396
  fetchOptions.body = bodyParamFileEl.files[0]; // eslint-disable-line prefer-destructuring
1376
- curlData = ` --data-binary @${bodyParamFileEl.files[0].name} \\\n`;
1377
1397
  }
1378
1398
  } else if (requestBodyType.includes('json') || requestBodyType.includes('xml') || requestBodyType.includes('text')) {
1379
1399
  const exampleTextAreaEl = requestPanelEl.querySelector('.request-body-param-user-input');
1380
1400
  if (exampleTextAreaEl?.value) {
1381
1401
  fetchOptions.body = exampleTextAreaEl.value;
1382
- if (requestBodyType.includes('json')) {
1383
- try {
1384
- curlData = ` -d '${JSON.stringify(JSON.parse(exampleTextAreaEl.value))}' \\\n`;
1385
- } catch (err) {
1386
- // Ignore.
1387
- }
1388
- }
1389
- if (!curlData) {
1390
- curlData = ` -d '${exampleTextAreaEl.value.replace(/'/g, '\'"\'"\'')}' \\\n`;
1391
- }
1392
1402
  }
1393
1403
  }
1394
- // Common for all request-body
1395
- if (!requestBodyType.includes('form-data')) {
1396
- // For multipart/form-data dont set the content-type to allow creation of browser generated part boundaries
1397
- reqHeaders.append('Content-Type', requestBodyType);
1398
- }
1399
- curlHeaders += ` -H "Content-Type: ${requestBodyType}" \\\n`;
1400
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);
1401
1415
  this.responseUrl = '';
1402
1416
  this.responseHeaders = [];
1403
- this.curlSyntax = '';
1417
+ this.curlSyntax = this.generateCURLSyntax(fetchUrl, reqHeaders, fetchOptions, requestPanelEl);
1404
1418
  this.responseStatus = 'success';
1405
1419
  this.responseIsBlob = false;
1406
1420
 
@@ -1409,22 +1423,28 @@ export default class ApiRequest extends LitElement {
1409
1423
  URL.revokeObjectURL(this.responseBlobUrl);
1410
1424
  this.responseBlobUrl = '';
1411
1425
  }
1412
- this.curlSyntax = `${curl}${curlHeaders}${curlData}${curlForm}`;
1413
1426
  if (this.fetchCredentials) {
1414
1427
  fetchOptions.credentials = this.fetchCredentials;
1415
1428
  }
1416
1429
  const controller = new AbortController();
1417
1430
  const { signal } = controller;
1418
1431
  fetchOptions.headers = reqHeaders;
1419
- const fetchRequest = new Request(fetchUrl, fetchOptions);
1432
+ const tempRequest = { url: fetchUrl, ...fetchOptions };
1420
1433
  this.dispatchEvent(new CustomEvent('before-try', {
1421
1434
  bubbles: true,
1422
1435
  composed: true,
1423
1436
  detail: {
1424
- request: fetchRequest,
1437
+ request: tempRequest,
1425
1438
  controller,
1426
1439
  },
1427
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);
1428
1448
 
1429
1449
  let fetchResponse;
1430
1450
  let responseClone;
@@ -1522,6 +1542,7 @@ export default class ApiRequest extends LitElement {
1522
1542
  },
1523
1543
  }));
1524
1544
  this.responseMessage = 'Request Aborted';
1545
+ this.responseText = 'Request Aborted';
1525
1546
  } else {
1526
1547
  this.dispatchEvent(new CustomEvent('after-try', {
1527
1548
  bubbles: true,
@@ -1537,6 +1558,90 @@ export default class ApiRequest extends LitElement {
1537
1558
  this.requestUpdate();
1538
1559
  }
1539
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
+
1540
1645
  onAddRemoveFileInput(e, pname, ptype) {
1541
1646
  if (e.target.tagName.toLowerCase() !== 'button') {
1542
1647
  return;
@@ -1592,7 +1697,8 @@ export default class ApiRequest extends LitElement {
1592
1697
  }
1593
1698
 
1594
1699
  disconnectedCallback() {
1595
- // 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
1596
1702
  if (this.responseBlobUrl) {
1597
1703
  URL.revokeObjectURL(this.responseBlobUrl);
1598
1704
  this.responseBlobUrl = '';