openapi-explorer 1.0.571 → 1.1.578

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.
@@ -7,7 +7,7 @@ import { keyed } from 'lit/directives/keyed.js';
7
7
  import formatXml from 'xml-but-prettier';
8
8
  import { copyToClipboard } from '../utils/common-utils';
9
9
  import { getI18nText } from '../languages';
10
- import { schemaInObjectNotation, getTypeInfo, generateExample } from '../utils/schema-utils';
10
+ import { schemaInObjectNotation, getTypeInfo, generateExample, isPatternProperty } from '../utils/schema-utils';
11
11
  import './json-tree';
12
12
  import './schema-tree';
13
13
  import getRequestFormTable from './request-form-table';
@@ -21,6 +21,7 @@ export default class ApiRequest extends LitElement {
21
21
 
22
22
  constructor() {
23
23
  super();
24
+ this.duplicatedRowsByKey = {};
24
25
  this.storedParamValues = {};
25
26
  this.responseMessage = '';
26
27
  this.responseStatus = '';
@@ -29,7 +30,7 @@ export default class ApiRequest extends LitElement {
29
30
  this.responseUrl = '';
30
31
  this.responseElapsedMs = 0;
31
32
  this.curlSyntax = '';
32
- this.activeResponseTab = 'response'; // allowed values: response, headers, curl
33
+ this.activeResponseTab = 'curl'; // allowed values: response, headers, curl
33
34
 
34
35
  this.selectedRequestBodyType = '';
35
36
  this.selectedRequestBodyExample = '';
@@ -138,6 +139,10 @@ export default class ApiRequest extends LitElement {
138
139
  attribute: 'fetch-credentials'
139
140
  },
140
141
  // properties for internal tracking
142
+ duplicatedRowsByKey: {
143
+ type: Object
144
+ },
145
+ // Tracking duplicated rows in form table
141
146
  activeResponseTab: {
142
147
  type: String
143
148
  },
@@ -161,13 +166,8 @@ export default class ApiRequest extends LitElement {
161
166
  }
162
167
 
163
168
  updated(changedProperties) {
164
- if (changedProperties.has('elementId')) {
165
- this.selectedRequestBodyType = '';
166
- this.selectedRequestBodyExample = '';
167
- } // In focused mode after rendering the request component, update the text-areas(which contains examples) using the original values from hidden textareas.
169
+ // In focused mode after rendering the request component, update the text-areas(which contains examples) using the original values from hidden textareas.
168
170
  // 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
169
-
170
-
171
171
  if (this.renderStyle !== 'focused') {
172
172
  return;
173
173
  } // dont update example as only tabs is switched
@@ -235,7 +235,10 @@ export default class ApiRequest extends LitElement {
235
235
 
236
236
  tableRows.push(html` <tr> <td colspan="1" style="width:160px;min-width:50px;vertical-align:top"> <div class="param-name ${paramSchema.deprecated ? 'deprecated' : ''}" style="margin-top:1rem"> ${param.name}${!paramSchema.deprecated && param.required ? html`<span style="color:var(--red)">*</span>` : ''} </div> <div class="param-type" style="margin-bottom:1rem"> ${paramSchema.type === 'array' ? `${paramSchema.arrayType}` : `${paramSchema.format ? paramSchema.format : paramSchema.type}`}${!paramSchema.deprecated && param.required ? html`<span style="opacity:0">*</span>` : ''} </div> </td> <td colspan="2" style="min-width:160px;vertical-align:top"> ${this.allowTry === 'true' ? paramSchema.type === 'array' && html` <div style="margin-top:1rem;margin-bottom:1rem"> <tag-input class="request-param" style="width:100%" data-ptype="${paramLocation}" data-pname="${param.name}" data-default="${Array.isArray(defaultVal) ? defaultVal.join('~|~') : defaultVal}" data-param-serialize-style="${paramStyle}" data-param-serialize-explode="${paramExplode}" data-array="true" placeholder="add-multiple ↩" @change="${e => {
237
237
  this.storedParamValues[param.name] = e.detail.value;
238
- }}" .value="${(_this$storedParamValu = this.storedParamValues[param.name]) !== null && _this$storedParamValu !== void 0 ? _this$storedParamValu : this.fillRequestWithDefault === 'true' && Array.isArray(defaultVal) ? defaultVal : defaultVal.split(',')}"></tag-input> </div>` || paramSchema.type === 'object' && html` <textarea class="textarea small request-param" part="textarea small textarea-param" rows="3" data-ptype="${paramLocation}" data-pname="${param.name}" data-default="${defaultVal}" data-param-serialize-style="${paramStyle}" data-param-serialize-explode="${paramExplode}" spellcheck="false" placeholder="${paramSchema.example || defaultVal || ''}" style="width:100%;margin-top:1rem;margin-bottom:1rem" .value="${this.fillRequestWithDefault === 'true' ? defaultVal : ''}"></textarea>` || html` <input type="${paramSchema.format === 'password' ? 'password' : 'text'}" spellcheck="false" style="width:100%;margin-top:1rem;margin-bottom:1rem" placeholder="${paramSchema.example || defaultVal || ''}" class="request-param" part="textbox textbox-param" data-ptype="${paramLocation}" data-pname="${param.name}" data-default="${Array.isArray(defaultVal) ? defaultVal.join('~|~') : defaultVal}" data-array="false" @keyup="${this.requestParamFunction}" .value="${this.fillRequestWithDefault === 'true' ? defaultVal : ''}">` : ''} ${this.exampleListTemplate.call(this, param, paramSchema.type)} </td> ${this.renderStyle === 'focused' ? html` <td colspan="2" style="vertical-align:top"> ${param.description ? html` <div class="param-description" style="margin-top:1rem"> ${unsafeHTML(marked(param.description))} </div>` : ''} ${paramSchema.default || paramSchema.s || paramSchema.allowedValues || paramSchema.pattern ? html` <div class="param-constraint" style="margin-top:1rem"> ${paramSchema.constraints.length ? html`<span style="font-weight:700">Constraints: </span>${paramSchema.constraints.join(', ')}<br>` : ''} ${paramSchema.pattern ? html` <div class="tooltip tooltip-replace" style="cursor:pointer;max-width:100%;display:flex"> <div style="white-space:nowrap;font-weight:700;margin-right:2px">Pattern: </div> <div style="white-space:nowrap;text-overflow:ellipsis;max-width:100%;overflow:hidden">${paramSchema.pattern}</div> <br> <div class="tooltip-text" style="position:absolute;display:block">${paramSchema.pattern}</div> </div> ` : ''} ${paramSchema.allowedValues && paramSchema.allowedValues.split('┃').map((v, i) => html` ${i > 0 ? '|' : html`<span style="font-weight:700">Allowed: </span>`} ${html` <a part="anchor anchor-param-constraint" class="${this.allowTry === 'true' ? '' : 'inactive-link'}" data-type="${paramSchema.type === 'array' ? 'array' : 'string'}" data-enum="${v.trim()}" @click="${e => {
238
+ this.computeCurlSyntax();
239
+ }}" .value="${(_this$storedParamValu = this.storedParamValues[param.name]) !== null && _this$storedParamValu !== void 0 ? _this$storedParamValu : this.fillRequestWithDefault === 'true' && Array.isArray(defaultVal) ? defaultVal : defaultVal.split(',')}"></tag-input> </div>` || paramSchema.type === 'object' && html` <textarea class="textarea small request-param" part="textarea small textarea-param" rows="3" data-ptype="${paramLocation}" data-pname="${param.name}" data-default="${defaultVal}" data-param-serialize-style="${paramStyle}" data-param-serialize-explode="${paramExplode}" spellcheck="false" placeholder="${paramSchema.example || defaultVal || ''}" style="width:100%;margin-top:1rem;margin-bottom:1rem" .value="${this.fillRequestWithDefault === 'true' ? defaultVal : ''}"></textarea>` || html` <input type="${paramSchema.format === 'password' ? 'password' : 'text'}" spellcheck="false" style="width:100%;margin-top:1rem;margin-bottom:1rem" @input="${() => {
240
+ this.computeCurlSyntax();
241
+ }}" placeholder="${paramSchema.example || defaultVal || ''}" class="request-param" part="textbox textbox-param" data-ptype="${paramLocation}" data-pname="${param.name}" data-default="${Array.isArray(defaultVal) ? defaultVal.join('~|~') : defaultVal}" data-array="false" @keyup="${this.requestParamFunction}" .value="${this.fillRequestWithDefault === 'true' ? defaultVal : ''}">` : ''} ${this.exampleListTemplate.call(this, param, paramSchema.type)} </td> ${this.renderStyle === 'focused' ? html` <td colspan="2" style="vertical-align:top"> ${param.description ? html` <div class="param-description" style="margin-top:1rem"> ${unsafeHTML(marked(param.description))} </div>` : ''} ${paramSchema.default || paramSchema.s || paramSchema.allowedValues || paramSchema.pattern ? html` <div class="param-constraint" style="margin-top:1rem"> ${paramSchema.constraints.length ? html`<span style="font-weight:700">Constraints: </span>${paramSchema.constraints.join(', ')}<br>` : ''} ${paramSchema.pattern ? html` <div class="tooltip tooltip-replace" style="cursor:pointer;max-width:100%;display:flex"> <div style="white-space:nowrap;font-weight:700;margin-right:2px">Pattern: </div> <div style="white-space:nowrap;text-overflow:ellipsis;max-width:100%;overflow:hidden">${paramSchema.pattern}</div> <br> <div class="tooltip-text" style="position:absolute;display:block">${paramSchema.pattern}</div> </div> ` : ''} ${paramSchema.allowedValues && paramSchema.allowedValues.split('┃').map((v, i) => html` ${i > 0 ? '|' : html`<span style="font-weight:700">Allowed: </span>`} ${html` <a part="anchor anchor-param-constraint" class="${this.allowTry === 'true' ? '' : 'inactive-link'}" data-type="${paramSchema.type === 'array' ? 'array' : 'string'}" data-enum="${v.trim()}" @click="${e => {
239
242
  const inputEl = e.target.closest('table').querySelector(`[data-pname="${param.name}"]`);
240
243
 
241
244
  if (inputEl) {
@@ -308,6 +311,7 @@ export default class ApiRequest extends LitElement {
308
311
  resetRequestBodySelection() {
309
312
  this.selectedRequestBodyType = '';
310
313
  this.selectedRequestBodyExample = '';
314
+ this.computeCurlSyntax();
311
315
  this.clearResponseData();
312
316
  } // Request-Body Event Handlers
313
317
 
@@ -319,6 +323,7 @@ export default class ApiRequest extends LitElement {
319
323
  const exampleTextareaEl = selectEl.closest('.example-panel').querySelector('.request-body-param');
320
324
  const userInputExampleTextareaEl = selectEl.closest('.example-panel').querySelector('.request-body-param-user-input');
321
325
  userInputExampleTextareaEl.value = exampleTextareaEl.value;
326
+ this.computeCurlSyntax();
322
327
  }, 0, exampleDropdownEl);
323
328
  }
324
329
 
@@ -333,6 +338,8 @@ export default class ApiRequest extends LitElement {
333
338
  const userInputExampleTextareaEl = selectEl.closest('.request-body-container').querySelector('.request-body-param-user-input');
334
339
  userInputExampleTextareaEl.value = exampleTextareaEl.value;
335
340
  }
341
+
342
+ this.computeCurlSyntax();
336
343
  }, 0, mimeDropdownEl);
337
344
  }
338
345
 
@@ -410,7 +417,7 @@ export default class ApiRequest extends LitElement {
410
417
  }
411
418
  }
412
419
 
413
- return html` <div class="request-body-container" data-selected-request-body-type="${this.selectedRequestBodyType}"> <div class="table-title top-gap row"> REQUEST BODY ${this.request_body.required ? html`<span class="mono-font" style="color:var(--red)">*</span>` : ''} <span style="font-weight:400;margin-left:5px"> ${this.selectedRequestBodyType}</span> <span style="flex:1"></span> ${reqBodyTypeSelectorHtml} </div> ${this.request_body.description ? html`<div class="m-markdown" style="margin-bottom:12px">${unsafeHTML(marked(this.request_body.description))}</div>` : ''} ${reqBodySchemaHtml || reqBodyDefaultHtml ? html` <div class="tab-panel col" style="border-width:0 0 1px 0"> <div class="tab-buttons row" @click="${e => {
420
+ return html` <div class="request-body-container" data-selected-request-body-type="${this.selectedRequestBodyType}"> <div class="table-title top-gap row"> ${getI18nText('operations.request-body')} ${this.request_body.required ? html`<span class="mono-font" style="color:var(--red)">*</span>` : ''} <span style="font-weight:400;margin-left:5px"> ${this.selectedRequestBodyType}</span> <span style="flex:1"></span> ${reqBodyTypeSelectorHtml} </div> ${this.request_body.description ? html`<div class="m-markdown" style="margin-bottom:12px">${unsafeHTML(marked(this.request_body.description))}</div>` : ''} ${reqBodySchemaHtml || reqBodyDefaultHtml ? html` <div class="tab-panel col" style="border-width:0 0 1px 0"> <div class="tab-buttons row" @click="${e => {
414
421
  if (e.target.tagName.toLowerCase() === 'button') {
415
422
  this.activeSchemaTab = e.target.dataset.tab;
416
423
  }
@@ -431,14 +438,16 @@ export default class ApiRequest extends LitElement {
431
438
 
432
439
 
433
440
  apiResponseTabTemplate() {
441
+ const curlSyntax = this.curlSyntax || this.computeCurlSyntax() || '';
442
+ const hasResponse = this.responseMessage !== '';
434
443
  const responseFormat = this.responseHeaders.includes('json') ? 'json' : this.responseHeaders.includes('html') || this.responseHeaders.includes('xml') ? 'html' : '';
435
- return html` <div class="row" style="font-size:var(--font-size-small);margin:5px 0"> ${this.responseMessage ? html`<div class="response-message ${this.responseStatus}">Response Status: ${this.responseMessage} ${this.responseElapsedMs ? html`<span><br>Execution Time: ${this.responseElapsedMs}ms</span>` : ''} </div>` : ''} <div style="flex:1"></div> <button class="m-btn" part="btn btn-outline" @click="${this.clearResponseData}">CLEAR RESPONSE</button> </div> <div class="tab-panel col" style="border-width:0 0 1px 0"> <div id="tab_buttons" class="tab-buttons row" @click="${e => {
444
+ return html` <div class="row" style="font-size:var(--font-size-small);margin:5px 0"> ${this.responseMessage ? html`<div class="response-message ${this.responseStatus}">Response Status: ${this.responseMessage} ${this.responseElapsedMs ? html`<span><br>Execution Time: ${this.responseElapsedMs}ms</span>` : ''} </div>` : ''} <div style="flex:1"></div> ${!hasResponse ? '' : html`<button class="m-btn" part="btn btn-outline" @click="${this.clearResponseData}">CLEAR RESPONSE</button>`} </div> <div class="tab-panel col" style="border-width:0 0 1px 0"> <div id="tab_buttons" class="tab-buttons row" @click="${e => {
436
445
  if (e.target.classList.contains('tab-btn') === false) {
437
446
  return;
438
447
  }
439
448
 
440
449
  this.activeResponseTab = e.target.dataset.tab;
441
- }}"> <button class="tab-btn ${this.activeResponseTab === 'response' ? 'active' : ''}" data-tab="response">${getI18nText('operations.response')}</button> <button class="tab-btn ${this.activeResponseTab === 'headers' ? 'active' : ''}" data-tab="headers">${getI18nText('operations.response-headers')}</button> <button class="tab-btn ${this.activeResponseTab === 'curl' ? 'active' : ''}" data-tab="curl">CURL</button> </div> ${this.responseIsBlob ? html` <div class="tab-content col" style="flex:1;display:${this.activeResponseTab === 'response' ? 'flex' : 'none'}"> <button class="m-btn thin-border mar-top-8" style="width:135px" @click="${this.downloadResponseBlob}" part="btn btn-outline">DOWNLOAD</button> ${this.responseBlobType === 'view' ? html`<button class="m-btn thin-border mar-top-8" style="width:135px" @click="${this.viewResponseBlob}" part="btn btn-outline">VIEW (NEW TAB)</button>` : ''} </div>` : html` <div class="tab-content col m-markdown" style="flex:1;display:${this.activeResponseTab === 'response' ? 'flex' : 'none'}"> ${this.responseText ? html`<button class="m-btn outline-primary toolbar-copy-btn" @click="${e => {
450
+ }}"> <br> <div style="width:100%"> ${!hasResponse ? '' : html` <button class="tab-btn ${this.activeResponseTab === 'response' ? 'active' : ''}" data-tab="response">${getI18nText('operations.response')}</button> <button class="tab-btn ${this.activeResponseTab === 'headers' ? 'active' : ''}" data-tab="headers">${getI18nText('operations.response-headers')}</button>`} <button class="tab-btn ${!hasResponse || this.activeResponseTab === 'curl' ? 'active' : ''}" data-tab="curl">FULL REQUEST</button> </div> </div> ${this.responseIsBlob ? html` <div class="tab-content col" style="flex:1;display:${this.activeResponseTab === 'response' ? 'flex' : 'none'}"> <button class="m-btn thin-border mar-top-8" style="width:135px" @click="${this.downloadResponseBlob}" part="btn btn-outline">DOWNLOAD</button> ${this.responseBlobType === 'view' ? html`<button class="m-btn thin-border mar-top-8" style="width:135px" @click="${this.viewResponseBlob}" part="btn btn-outline">VIEW (NEW TAB)</button>` : ''} </div>` : html` <div class="tab-content col m-markdown" style="flex:1;display:${this.activeResponseTab === 'response' ? 'flex' : 'none'}"> ${this.responseText ? html`<button class="m-btn outline-primary toolbar-copy-btn" @click="${e => {
442
451
  copyToClipboard(this.responseText, e);
443
452
  }}" part="btn btn-fill">${getI18nText('operations.copy')}</button>` : ''} <pre style="min-height:60px" @copy="${() => {
444
453
  copyToClipboard(window.getSelection().toString());
@@ -446,14 +455,14 @@ export default class ApiRequest extends LitElement {
446
455
  </pre> </div>`} <div class="tab-content col m-markdown" style="flex:1;display:${this.activeResponseTab === 'headers' ? 'flex' : 'none'}"> <button class="m-btn outline-primary toolbar-copy-btn" @click="${e => {
447
456
  copyToClipboard(this.responseHeaders, e);
448
457
  }}" part="btn btn-fill">${getI18nText('operations.copy')}</button> <pre><code>${unsafeHTML(Prism.highlight(this.responseHeaders, Prism.languages.css, 'css'))}</code></pre> </div> <div class="tab-content col m-markdown" style="flex:1;display:${this.activeResponseTab === 'curl' ? 'flex' : 'none'}"> <button class="m-btn outline-primary toolbar-copy-btn" @click="${e => {
449
- copyToClipboard(this.curlSyntax, e);
458
+ copyToClipboard(curlSyntax, e);
450
459
  }}" part="btn btn-fill">${getI18nText('operations.copy')}</button> <pre class="fs-exclude" data-hj-suppress data-sl="mask">
451
- <code>${unsafeHTML(Prism.highlight(this.curlSyntax.trim(), Prism.languages.shell, 'shell'))}</code>
460
+ <code>${unsafeHTML(Prism.highlight(curlSyntax.trim(), Prism.languages.shell, 'shell'))}</code>
452
461
  </pre> </div> </div>`;
453
462
  }
454
463
 
455
464
  apiCallTemplate() {
456
- return html` <div style="display:flex;align-items:flex-end;margin:16px 0;font-size:var(--font-size-small)"> ${this.parameters.length > 0 || this.request_body ? html` <button class="m-btn thin-border" part="btn btn-outline" style="margin-right:5px" @click="${this.onClearRequestData}"> ${getI18nText('operations.clear')} </button>` : ''} <button class="m-btn primary btn-execute thin-border" part="btn btn-fill btn-try" @click="${this.onTryClick}">${getI18nText('operations.execute')}</button> </div> ${this.responseMessage === '' ? '' : this.apiResponseTabTemplate()} `;
465
+ return html` <div style="display:flex;align-items:flex-end;margin:16px 0;font-size:var(--font-size-small)"> ${this.parameters.length > 0 || this.request_body ? html` <button class="m-btn thin-border" part="btn btn-outline" style="margin-right:5px" @click="${this.onClearRequestData}"> ${getI18nText('operations.clear')} </button>` : ''} <button class="m-btn primary btn-execute thin-border" part="btn btn-fill btn-try" @click="${this.onTryClick}">${getI18nText('operations.execute')}</button> </div> ${this.apiResponseTabTemplate()} `;
457
466
  }
458
467
  /* eslint-enable indent */
459
468
 
@@ -477,12 +486,10 @@ export default class ApiRequest extends LitElement {
477
486
  }
478
487
  };
479
488
  this.dispatchEvent(new CustomEvent('event', event));
489
+ this.computeCurlSyntax();
480
490
  }
481
491
 
482
- async onTryClick() {
483
- const tryBtnEl = this.querySelectorAll('.btn-execute')[0];
484
- let curlData = '';
485
- let curlForm = '';
492
+ recomputeFetchOptions() {
486
493
  const closestRespContainer = this.closest('.expanded-req-resp-container, .req-resp-container');
487
494
  const respEl = closestRespContainer && closestRespContainer.getElementsByTagName('api-response')[0];
488
495
  const acceptHeader = respEl === null || respEl === void 0 ? void 0 : respEl.selectedMimeType;
@@ -602,14 +609,21 @@ export default class ApiRequest extends LitElement {
602
609
  fetchOptions.headers.append(el.dataset.pname, el.value);
603
610
  }
604
611
  }); // Request Body Params
612
+ // url-encoded Form Params (dynamic) - Parse JSON and generate Params
613
+
614
+ const formUrlDynamicTextAreaEl = requestPanelEl.querySelector("[data-ptype='dynamic-form']"); // url-encoded Form Params (regular)
615
+
616
+ const rawFormInputEls = [...requestPanelEl.querySelectorAll("[data-ptype='form-input']")];
617
+ const patternPropertyKeyEls = [...requestPanelEl.querySelectorAll("[data-ptype='pattern-property-key']")];
618
+ const patternPropertyInputEls = rawFormInputEls.filter(el => isPatternProperty(el.dataset.pname));
619
+ const formInputEls = rawFormInputEls.filter(el => !isPatternProperty(el.dataset.pname));
620
+ let curlData = '';
621
+ let curlForm = '';
605
622
 
606
623
  if (requestBodyContainerEl) {
607
624
  const requestBodyType = requestBodyContainerEl.dataset.selectedRequestBodyType;
608
625
 
609
626
  if (requestBodyType.includes('form-urlencoded')) {
610
- // url-encoded Form Params (dynamic) - Parse JSON and generate Params
611
- const formUrlDynamicTextAreaEl = requestPanelEl.querySelector("[data-ptype='dynamic-form']");
612
-
613
627
  if (formUrlDynamicTextAreaEl) {
614
628
  const val = formUrlDynamicTextAreaEl.value;
615
629
  const formUrlDynParams = new URLSearchParams();
@@ -636,17 +650,23 @@ export default class ApiRequest extends LitElement {
636
650
  curlData = ` \\\n -d ${formUrlDynParams.toString()}`;
637
651
  }
638
652
  } else {
639
- // url-encoded Form Params (regular)
640
- const formUrlEls = [...requestPanelEl.querySelectorAll("[data-ptype='form-urlencode']")];
641
653
  const formUrlParams = new URLSearchParams();
642
- formUrlEls.filter(v => v.type !== 'file').forEach(el => {
654
+ patternPropertyInputEls.concat(formInputEls).forEach((el, counter) => {
655
+ var _patternPropertyKeyEl;
656
+
657
+ const keyName = ((_patternPropertyKeyEl = patternPropertyKeyEls[counter]) === null || _patternPropertyKeyEl === void 0 ? void 0 : _patternPropertyKeyEl.value) || el.dataset.pname;
658
+
659
+ if (el.type === 'file') {
660
+ return;
661
+ }
662
+
643
663
  if (el.dataset.array === 'false') {
644
664
  if (el.value) {
645
- formUrlParams.append(el.dataset.pname, el.value);
665
+ formUrlParams.append(keyName, el.value);
646
666
  }
647
667
  } else {
648
668
  const vals = el.value && Array.isArray(el.value) ? el.value.join(',') : '';
649
- formUrlParams.append(el.dataset.pname, vals);
669
+ formUrlParams.append(keyName, vals);
650
670
  }
651
671
  });
652
672
  fetchOptions.body = formUrlParams;
@@ -654,21 +674,24 @@ export default class ApiRequest extends LitElement {
654
674
  }
655
675
  } else if (requestBodyType.includes('form-data')) {
656
676
  const formDataParams = new FormData();
657
- const formDataEls = [...requestPanelEl.querySelectorAll("[data-ptype='form-data']")];
658
- formDataEls.forEach(el => {
677
+ patternPropertyInputEls.concat(formInputEls).forEach((el, counter) => {
678
+ var _patternPropertyKeyEl2;
679
+
680
+ const keyName = ((_patternPropertyKeyEl2 = patternPropertyKeyEls[counter]) === null || _patternPropertyKeyEl2 === void 0 ? void 0 : _patternPropertyKeyEl2.value) || el.dataset.pname;
681
+
659
682
  if (el.dataset.array === 'false') {
660
683
  if (el.type === 'file' && el.files[0]) {
661
- formDataParams.append(el.dataset.pname, el.files[0], el.files[0].name);
662
- curlForm += ` \\\n -F "${el.dataset.pname}=@${el.files[0].name}"`;
684
+ formDataParams.append(keyName, el.files[0], el.files[0].name);
685
+ curlForm += ` \\\n -F "${keyName}=@${el.files[0].name}"`;
663
686
  } else if (el.value) {
664
- formDataParams.append(el.dataset.pname, el.value);
665
- curlForm += ` \\\n -F "${el.dataset.pname}=${el.value}"`;
687
+ formDataParams.append(keyName, el.value);
688
+ curlForm += ` \\\n -F "${keyName}=${el.value}"`;
666
689
  }
667
690
  } else if (el.value && Array.isArray(el.value)) {
668
691
  el.value.forEach(v => {
669
- curlForm += ` \\\n -F "${el.dataset.pname}[]=${v}"`;
692
+ curlForm += ` \\\n -F "${keyName}[]=${v}"`;
670
693
  });
671
- formDataParams.append(el.dataset.pname, el.value.join(','));
694
+ formDataParams.append(keyName, el.value.join(','));
672
695
  }
673
696
  });
674
697
  fetchOptions.body = formDataParams;
@@ -707,16 +730,46 @@ export default class ApiRequest extends LitElement {
707
730
  }
708
731
  }
709
732
 
733
+ if (this.fetchCredentials) {
734
+ fetchOptions.credentials = this.fetchCredentials;
735
+ }
736
+
737
+ return {
738
+ fetchOptions,
739
+ fetchUrl,
740
+ curlParts: {
741
+ data: curlData,
742
+ form: curlForm
743
+ }
744
+ };
745
+ }
746
+
747
+ computeCurlSyntax(headerOverride) {
748
+ const {
749
+ fetchOptions,
750
+ fetchUrl,
751
+ curlParts
752
+ } = this.recomputeFetchOptions();
753
+ const curl = `curl -X ${this.method.toUpperCase()} "${fetchUrl.toString()}"`;
754
+ const headers = headerOverride !== null && headerOverride !== void 0 ? headerOverride : fetchOptions.headers;
755
+ const curlHeaders = [...headers.entries()].reduce((acc, [key, value]) => `${acc} \\\n -H "${key}: ${value}"`, '');
756
+ this.curlSyntax = `${curl}${curlHeaders}${curlParts.data}${curlParts.form}`;
757
+ this.requestUpdate();
758
+ } // onExecuteButtonClicked
759
+
760
+
761
+ async onTryClick() {
762
+ const tryBtnEl = this.querySelectorAll('.btn-execute')[0];
763
+ const {
764
+ fetchOptions,
765
+ fetchUrl
766
+ } = this.recomputeFetchOptions();
710
767
  this.responseIsBlob = false;
711
768
  this.respContentDisposition = '';
712
769
 
713
770
  if (this.responseBlobUrl) {
714
771
  URL.revokeObjectURL(this.responseBlobUrl);
715
772
  this.responseBlobUrl = '';
716
- }
717
-
718
- if (this.fetchCredentials) {
719
- fetchOptions.credentials = this.fetchCredentials;
720
773
  } // Options is legacy usage, documentation has been updated to reference properties of the fetch option directly, but older usages may still be using options
721
774
 
722
775
 
@@ -742,9 +795,7 @@ export default class ApiRequest extends LitElement {
742
795
  body: fetchRequest.body || fetchOptions.body
743
796
  };
744
797
  const fetchRequestObject = new Request(fetchRequest.url, newFetchOptions);
745
- const curl = `curl -X ${this.method.toUpperCase()} "${fetchUrl.toString()}"`;
746
- const curlHeaders = [...newFetchOptions.headers.entries()].reduce((acc, [key, value]) => `${acc} \\\n -H "${key}: ${value}"`, '');
747
- this.curlSyntax = `${curl}${curlHeaders}${curlData}${curlForm}`;
798
+ this.computeCurlSyntax(newFetchOptions.headers);
748
799
  let fetchResponse;
749
800
 
750
801
  try {
@@ -758,6 +809,7 @@ export default class ApiRequest extends LitElement {
758
809
  this.responseUrl = '';
759
810
  this.responseHeaders = '';
760
811
  this.responseText = '⌛';
812
+ this.activeResponseTab = 'response';
761
813
  this.requestUpdate();
762
814
  const awaiter = new Promise(resolve => setTimeout(resolve, 200));
763
815
  fetchResponse = await fetch(fetchRequestObject);
@@ -864,7 +916,7 @@ export default class ApiRequest extends LitElement {
864
916
  }
865
917
  }
866
918
 
867
- onAddRemoveFileInput(e, pname, ptype) {
919
+ onAddRemoveFileInput(e, pname) {
868
920
  if (e.target.tagName.toLowerCase() !== 'button') {
869
921
  return;
870
922
  }
@@ -886,7 +938,7 @@ export default class ApiRequest extends LitElement {
886
938
  newInputEl.type = 'file';
887
939
  newInputEl.setAttribute('class', 'file-input');
888
940
  newInputEl.setAttribute('data-pname', pname);
889
- newInputEl.setAttribute('data-ptype', ptype.includes('form-urlencode') ? 'form-urlencode' : 'form-data');
941
+ newInputEl.setAttribute('data-ptype', 'form-input');
890
942
  newInputEl.setAttribute('data-array', 'false');
891
943
  newInputEl.setAttribute('data-file-array', 'true'); // Remover Button
892
944
 
@@ -896,6 +948,8 @@ export default class ApiRequest extends LitElement {
896
948
  newInputContainerEl.appendChild(newInputEl);
897
949
  newInputContainerEl.appendChild(newRemoveBtnEl);
898
950
  el.insertBefore(newInputContainerEl, e.target); // el.appendChild(newInputContainerEl);
951
+
952
+ this.computeCurlSyntax();
899
953
  }
900
954
 
901
955
  downloadResponseBlob() {
@@ -2,6 +2,9 @@
2
2
  import { html } from 'lit';
3
3
  import { marked } from 'marked';
4
4
  import { unsafeHTML } from 'lit/directives/unsafe-html.js';
5
+ import { isPatternProperty } from '../utils/schema-utils';
6
+ import { map } from 'lit/directives/map.js';
7
+ import { range } from 'lit/directives/range.js';
5
8
 
6
9
  function generateFormRows(data, options, dataType = 'object', key = '', description = '', schemaLevel = 0) {
7
10
  const newSchemaLevel = data['::type'] && data['::type'].startsWith('xxx-of') ? schemaLevel : schemaLevel + 1;
@@ -45,6 +48,20 @@ function generateFormRows(data, options, dataType = 'object', key = '', descript
45
48
 
46
49
 
47
50
  const parsedData = JSON.parse(data);
51
+ return generatePrimitiveRow.call(this, parsedData, {
52
+ key,
53
+ keyLabel,
54
+ keyDescr,
55
+ description,
56
+ dataType,
57
+ isRequired,
58
+ options
59
+ });
60
+ }
61
+
62
+ function generatePrimitiveRow(rowData, parentRecursionOptions) {
63
+ var _this$duplicatedRowsB;
64
+
48
65
  const {
49
66
  type,
50
67
  format,
@@ -57,13 +74,46 @@ function generateFormRows(data, options, dataType = 'object', key = '', descript
57
74
  schemaDescription,
58
75
  schemaTitle,
59
76
  deprecated
60
- } = parsedData;
77
+ } = rowData;
78
+ const {
79
+ key,
80
+ keyLabel,
81
+ keyDescr,
82
+ description,
83
+ dataType,
84
+ isRequired,
85
+ options
86
+ } = parentRecursionOptions;
61
87
 
62
88
  if (readOrWriteOnly === '🆁') {
63
89
  return undefined;
64
90
  }
65
91
 
66
- return html` <tr> <td style="width:160px;min-width:100px"> <div class="param-name ${deprecated ? 'deprecated' : ''}"> ${!deprecated && isRequired ? html`<span class="key-label">${keyLabel}</span><span style="color:var(--red)">*</span>` : key.startsWith('::OPTION') ? html`<span class="xxx-of-key">${keyLabel}</span><span class="xxx-of-descr">${keyDescr}</span>` : html`${keyLabel ? html`<span class="key-label"> ${keyLabel}</span>` : html`<span class="xxx-of-descr">${schemaTitle}</span>`}`} </div> <div class="param-type"> ${dataType === 'array' ? html`[<span>${format || type}</span>]` : `${format || type}`} </div> </td> ${dataType === 'array' ? getArrayFormField.call(this, keyLabel, example, defaultValue, format, options) : ''} ${dataType !== 'array' ? getPrimitiveFormField.call(this, keyLabel, example, defaultValue, format, options) : ''} <td> ${description ? html`<div class="param-description">${unsafeHTML(marked(description))}</div>` : ''} ${defaultValue || constraints || allowedValues || pattern ? html` <div class="param-constraint"> ${pattern ? html`<span style="font-weight:700">Pattern: </span>${pattern}<br>` : ''} ${constraints.length ? html`<span style="font-weight:700">Constraints: </span>${constraints.join(', ')}<br>` : ''} ${allowedValues === null || allowedValues === void 0 ? void 0 : allowedValues.split('┃').map((v, i) => html` ${i > 0 ? '|' : html`<span style="font-weight:700">Allowed: </span>`} ${html` <a part="anchor anchor-param-constraint" data-type="${type === 'array' ? type : 'string'}" data-enum="${v.trim()}" @click="${e => {
92
+ const elementId = this.elementId || `${this.method}-${this.path}`;
93
+ const duplicateRowGeneratorKey = `${elementId}-${key}`;
94
+
95
+ const rowGenerator = e => {
96
+ var _e$target$dataset, _e$target$dataset2;
97
+
98
+ if (((_e$target$dataset = e.target.dataset) === null || _e$target$dataset === void 0 ? void 0 : _e$target$dataset.ptype) !== 'pattern-property-key' && !isPatternProperty((_e$target$dataset2 = e.target.dataset) === null || _e$target$dataset2 === void 0 ? void 0 : _e$target$dataset2.pname)) {
99
+ return;
100
+ } // If the row key has a value then add another row
101
+
102
+
103
+ const patternPropertyKeyEls = [...this.querySelectorAll("[data-ptype='pattern-property-key']")];
104
+ const patternPropertyInputEls = [...this.querySelectorAll("[data-ptype='form-input']")].filter(el => isPatternProperty(el.dataset.pname)); // If there is still some row that either has an empty key or an empty value, then skip adding a new row
105
+
106
+ if (patternPropertyKeyEls.some((keyElement, index) => !keyElement.value || !patternPropertyInputEls[index].value)) {
107
+ return;
108
+ }
109
+
110
+ if (e.target.value) {
111
+ this.duplicatedRowsByKey[duplicateRowGeneratorKey] = (this.duplicatedRowsByKey[duplicateRowGeneratorKey] || 1) + 1;
112
+ this.requestUpdate();
113
+ }
114
+ };
115
+
116
+ return map(range(((_this$duplicatedRowsB = this.duplicatedRowsByKey) === null || _this$duplicatedRowsB === void 0 ? void 0 : _this$duplicatedRowsB[duplicateRowGeneratorKey]) || 1), () => html` <tr> ${inputFieldKeyLabel.call(this, key.startsWith('::OPTION'), keyLabel, keyDescr, dataType, deprecated, isRequired, schemaTitle, format || type, rowGenerator)} ${dataType === 'array' ? getArrayFormField.call(this, keyLabel, example, defaultValue, format, rowGenerator) : ''} ${dataType !== 'array' ? getPrimitiveFormField.call(this, keyLabel, example, defaultValue, format, options, rowGenerator) : ''} <td> ${description ? html`<div class="param-description">${unsafeHTML(marked(description))}</div>` : ''} ${defaultValue || constraints || allowedValues || pattern ? html` <div class="param-constraint"> ${pattern ? html`<span style="font-weight:700">Pattern: </span>${pattern}<br>` : ''} ${constraints.length ? html`<span style="font-weight:700">Constraints: </span>${constraints.join(', ')}<br>` : ''} ${allowedValues === null || allowedValues === void 0 ? void 0 : allowedValues.split('┃').filter(v => v !== '').map((v, i) => html` ${i > 0 ? '|' : html`<span style="font-weight:700">Allowed: </span>`} ${html` <a part="anchor anchor-param-constraint" data-type="${type === 'array' ? type : 'string'}" data-enum="${v.trim()}" @click="${e => {
67
117
  const inputEl = e.target.closest('table').querySelector(`[data-pname="${keyLabel}"]`);
68
118
 
69
119
  if (inputEl) {
@@ -75,7 +125,17 @@ function generateFormRows(data, options, dataType = 'object', key = '', descript
75
125
  if (inputEl) {
76
126
  inputEl.value = e.target.dataset.exampleType === 'array' ? e.target.dataset.example.split('~|~') : e.target.dataset.example;
77
127
  }
78
- }}"> ${type === 'array' ? example.join(', ') : example} </a> ${type === 'array' ? '] ' : ''} </span>` : ''} </td> </tr>` : ''}`;
128
+ }}"> ${type === 'array' ? example.join(', ') : example} </a> ${type === 'array' ? '] ' : ''} </span>` : ''} </td> </tr>` : ''}`);
129
+ }
130
+
131
+ function inputFieldKeyLabel(isOption, keyLabel, keyDescription, dataType, deprecated, isRequired, schemaTitle, format, rowGenerator) {
132
+ if (isPatternProperty(keyLabel)) {
133
+ return html` <td style="width:160px;min-width:100px"> <div class="param-name ${deprecated ? 'deprecated' : ''}"> <input placeholder="${keyLabel}" @change="${e => {
134
+ rowGenerator(e);
135
+ }}" .value="${''}" spellcheck="false" type="${format === 'binary' ? 'file' : format === 'password' ? 'password' : 'text'}" part="textbox textbox-param" style="width:100%" data-ptype="pattern-property-key" data-pname="${keyLabel}" data-default="${''}" data-array="false"> </div></td>`;
136
+ }
137
+
138
+ return html` <td style="width:160px;min-width:100px"> <div class="param-name ${deprecated ? 'deprecated' : ''}"> ${!deprecated && isRequired ? html`<span class="key-label">${keyLabel}</span><span style="color:var(--red)">*</span>` : isOption ? html`<span class="xxx-of-key">${keyLabel}</span><span class="xxx-of-descr">${keyDescription}</span>` : html`${keyLabel ? html`<span class="key-label"> ${keyLabel}</span>` : html`<span class="xxx-of-descr">${schemaTitle}</span>`}`} </div> <div class="param-type"> ${dataType === 'array' ? html`[<span>${format}</span>]` : `${format}`} </div> </td>`;
79
139
  } // function getObjectFormField(keyLabel, example, defaultValue, format, options) {
80
140
  // return html`
81
141
  // <td>
@@ -86,30 +146,36 @@ function generateFormRows(data, options, dataType = 'object', key = '', descript
86
146
  // part = "textarea textarea-param"
87
147
  // style = "width:100%; border:none; resize:vertical;"
88
148
  // data-array = "false"
89
- // data-ptype = "${options.mimeType.includes('form-urlencode') ? 'form-urlencode' : 'form-data'}"
149
+ // data-ptype = "form-input"
90
150
  // data-pname = "${keyLabel}"
91
151
  // data-default = "${defaultValue || ''}"
92
152
  // spellcheck = "false"
93
153
  // .value="${options.fillRequestWithDefault === 'true' ? defaultValue : ''}"
94
154
  // ></textarea>
95
155
  // <!-- This textarea(hidden) is to store the original example value, in focused mode on navbar change it is used to update the example text -->
96
- // <textarea data-pname = "hidden-${keyLabel}" data-ptype = "${options.mimeType.includes('form-urlencode') ? 'hidden-form-urlencode' : 'hidden-form-data'}" class="is-hidden" style="display:none" .value="${defaultValue}"></textarea>
156
+ // <textarea data-pname = "hidden-${keyLabel}" data-ptype = "hidden-form-input" class="is-hidden" style="display:none" .value="${defaultValue}"></textarea>
97
157
  // </div>
98
158
  // </div>
99
159
  // </td>`;
100
160
  // }
101
161
 
102
162
 
103
- function getArrayFormField(keyLabel, example, defaultValue, format, options) {
163
+ function getArrayFormField(keyLabel, example, defaultValue, format, rowGenerator) {
104
164
  if (format === 'binary') {
105
- return html`<td style="min-width:100px"> <div class="file-input-container col" style="align-items:flex-end" @click="${e => this.onAddRemoveFileInput(e, keyLabel, options.mimeType)}"> <div class="input-set row"> <input type="file" part="file-input" class="file-input" data-pname="${keyLabel}" data-ptype="${options.mimeType.includes('form-urlencode') ? 'form-urlencode' : 'form-data'}" data-array="false" data-file-array="true"> <button class="file-input-remove-btn"> &#x2715; </button> </div> <button class="m-btn primary file-input-add-btn" part="btn btn-fill" style="margin:2px 25px 0 0;padding:2px 6px">ADD</button> </div> </td>`;
165
+ return html`<td style="min-width:100px"> <div class="file-input-container col" style="align-items:flex-end" @click="${e => this.onAddRemoveFileInput(e, keyLabel)}"> <div class="input-set row"> <input @change="${e => {
166
+ rowGenerator(e);
167
+ }}" type="file" part="file-input" class="file-input" data-pname="${keyLabel}" data-ptype="form-input" data-array="false" data-file-array="true"> <button class="file-input-remove-btn"> &#x2715; </button> </div> <button class="m-btn primary file-input-add-btn" part="btn btn-fill" style="margin:2px 25px 0 0;padding:2px 6px">ADD</button> </div> </td>`;
106
168
  }
107
169
 
108
- return html`<td style="min-width:100px"> <tag-input style="width:100%" data-ptype="${options.mimeType.includes('form-urlencode') ? 'form-urlencode' : 'form-data'}" data-pname="${keyLabel}" data-default="${defaultValue || ''}" data-array="true" placeholder="${(Array.isArray(example) ? example[0] : example) || defaultValue || 'add-multiple ↩'}" .value="${defaultValue || ''}"></tag-input> </td>`;
170
+ return html`<td style="min-width:100px"> <tag-input @change="${e => {
171
+ rowGenerator(e);
172
+ }}" style="width:100%" data-ptype="form-input" data-pname="${keyLabel}" data-default="${defaultValue || ''}" data-array="true" placeholder="${(Array.isArray(example) ? example[0] : example) || defaultValue || 'add-multiple ↩'}" .value="${defaultValue || ''}"></tag-input> </td>`;
109
173
  }
110
174
 
111
- function getPrimitiveFormField(keyLabel, example, defaultValue, format, options) {
112
- return html`<td style="min-width:100px"> <input placeholder="${example || defaultValue || ''}" .value="${options.fillRequestWithDefault && defaultValue || ''}" spellcheck="false" type="${format === 'binary' ? 'file' : format === 'password' ? 'password' : 'text'}" part="textbox textbox-param" style="width:100%" data-ptype="${options.mimeType.includes('form-urlencode') ? 'form-urlencode' : 'form-data'}" data-pname="${keyLabel}" data-default="${defaultValue || ''}" data-array="false"> </td>`;
175
+ function getPrimitiveFormField(keyLabel, example, defaultValue, format, options, rowGenerator) {
176
+ return html`<td style="min-width:100px"> <input placeholder="${example || defaultValue || ''}" @change="${e => {
177
+ rowGenerator(e);
178
+ }}" .value="${options.fillRequestWithDefault && defaultValue || ''}" spellcheck="false" type="${format === 'binary' ? 'file' : format === 'password' ? 'password' : 'text'}" part="textbox textbox-param" style="width:100%" data-ptype="form-input" data-pname="${keyLabel}" data-default="${defaultValue || ''}" data-array="false"> </td>`;
113
179
  }
114
180
 
115
181
  export default function getRequestFormTable(data, mimeType) {
@@ -117,5 +183,5 @@ export default function getRequestFormTable(data, mimeType) {
117
183
  mimeType: mimeType,
118
184
  fillRequestWithDefault: this.fillRequestWithDefault === 'true'
119
185
  };
120
- return html` <table role="presentation" class="request-form-table" style="border:1px solid var(--light-border-color);width:100%"> ${data ? html`${generateFormRows.call(this, data['::type'] === 'array' ? data['::props'] : data, options, data['::type'])}` : ''} </table>`;
186
+ return html` <table id="request-form-table" role="presentation" class="request-form-table" style="border:1px solid var(--light-border-color);width:100%"> ${data ? html`${generateFormRows.call(this, data['::type'] === 'array' ? data['::props'] : data, options, data['::type'])}` : ''} </table>`;
121
187
  }
@@ -385,6 +385,10 @@ function getSimpleValueResult(schema, config, namespace, prefix, xmlAttributes,
385
385
  const value = getSampleValueByType(schema, config.propertyName, config.skipExampleStrings);
386
386
  return [value];
387
387
  }
388
+
389
+ export function isPatternProperty(label) {
390
+ return label.match(/^<any-key>|<pattern:/);
391
+ }
388
392
  /**
389
393
  * For changing OpenAPI-Schema to an Object Notation,
390
394
  * This Object would further be an input to UI Components to generate an Object-Tree
@@ -394,7 +398,6 @@ function getSimpleValueResult(schema, config, namespace, prefix, xmlAttributes,
394
398
  * @param {string} suffix - used for suffixing property names to avoid duplicate props during object composition
395
399
  */
396
400
 
397
-
398
401
  export function schemaInObjectNotation(rawSchema, options, level = 0, suffix = '') {
399
402
  if (!rawSchema) {
400
403
  return undefined;