rapidoc 11.0.1 → 12.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +171 -54
  2. package/dist/rapidoc-flow-min.js +580 -0
  3. package/dist/rapidoc-flow-min.js.LICENSE.txt +171 -0
  4. package/dist/rapidoc-min.js +645 -202
  5. package/dist/rapidoc-min.js.LICENSE.txt +381 -0
  6. package/package.json +57 -42
  7. package/src/README.md +150 -0
  8. package/src/components/README.md +91 -0
  9. package/src/components/api-request.ts +912 -0
  10. package/src/components/api-response.ts +514 -0
  11. package/src/components/json-tree.ts +171 -0
  12. package/src/components/schema-base-element.ts +329 -0
  13. package/src/components/schema-table.ts +475 -0
  14. package/src/components/schema-tree.ts +393 -0
  15. package/src/components/tag-input.ts +180 -0
  16. package/src/flow.js +3 -0
  17. package/src/index.js +6 -0
  18. package/src/oauth-receiver.ts +29 -0
  19. package/src/rapi-flow.ts +1178 -0
  20. package/src/rapidoc-mini.ts +603 -0
  21. package/src/rapidoc.ts +1854 -0
  22. package/src/styles/README.md +96 -0
  23. package/src/styles/auth-styles.js +318 -0
  24. package/src/styles/border-styles.js +24 -0
  25. package/src/styles/css-bundle.js +129 -0
  26. package/src/styles/custom-styles.js +8 -0
  27. package/src/styles/dialog-box-styles.js +102 -0
  28. package/src/styles/endpoint-styles.js +564 -0
  29. package/src/styles/flex-styles.js +16 -0
  30. package/src/styles/flow-styles.js +248 -0
  31. package/src/styles/font-styles.js +432 -0
  32. package/src/styles/info-styles.js +36 -0
  33. package/src/styles/input-styles.js +555 -0
  34. package/src/styles/layout-styles.js +396 -0
  35. package/src/styles/microlighter-styles.js +99 -0
  36. package/src/styles/nav-styles.js +633 -0
  37. package/src/styles/param-list-styles.js +261 -0
  38. package/src/styles/schema-styles.js +296 -0
  39. package/src/styles/tab-styles.js +106 -0
  40. package/src/styles/theme-tokens.js +582 -0
  41. package/src/templates/README.md +89 -0
  42. package/src/templates/advanced-search-dialog-template.js +94 -0
  43. package/src/templates/api-response-template.js +223 -0
  44. package/src/templates/callback-template.js +86 -0
  45. package/src/templates/code-samples-template.js +52 -0
  46. package/src/templates/components-template.js +86 -0
  47. package/src/templates/downloads-template.js +70 -0
  48. package/src/templates/endpoint-template.js +377 -0
  49. package/src/templates/expanded-endpoint-template.js +270 -0
  50. package/src/templates/focused-endpoint-template.js +146 -0
  51. package/src/templates/header-template.js +92 -0
  52. package/src/templates/logo-template.js +21 -0
  53. package/src/templates/main-body-template.js +102 -0
  54. package/src/templates/navbar-template.js +498 -0
  55. package/src/templates/overview-template.js +104 -0
  56. package/src/templates/request-body-template.js +673 -0
  57. package/src/templates/request-params-template.js +485 -0
  58. package/src/templates/security-scheme-template.js +1272 -0
  59. package/src/templates/server-template.js +153 -0
  60. package/src/utils/README.md +146 -0
  61. package/src/utils/arazzo/README.md +119 -0
  62. package/src/utils/arazzo/components.js +15 -0
  63. package/src/utils/arazzo/graph.js +269 -0
  64. package/src/utils/arazzo/index.js +52 -0
  65. package/src/utils/arazzo/operations.js +106 -0
  66. package/src/utils/arazzo/parse.js +196 -0
  67. package/src/utils/arazzo/refs.js +40 -0
  68. package/src/utils/arazzo/svg.js +707 -0
  69. package/src/utils/auto-color-scheme.js +99 -0
  70. package/src/utils/bind-navbar.js +175 -0
  71. package/src/utils/color-utils.js +59 -0
  72. package/src/utils/common-utils.js +191 -0
  73. package/src/utils/curl-utils.js +126 -0
  74. package/src/utils/highlighter.js +419 -0
  75. package/src/utils/markdown-utils.js +110 -0
  76. package/src/utils/mcp/README.md +155 -0
  77. package/src/utils/mcp/common.js +166 -0
  78. package/src/utils/mcp/manifest.js +382 -0
  79. package/src/utils/mcp/project/server.ts +53 -0
  80. package/src/utils/mcp/project.js +88 -0
  81. package/src/utils/mcp/runtime.js +289 -0
  82. package/src/utils/mcp/schema.js +98 -0
  83. package/src/utils/mcp/workflow-runtime.js +374 -0
  84. package/src/utils/mcp/workflows.js +240 -0
  85. package/src/utils/mcp/zip.js +102 -0
  86. package/src/utils/mcp-generator.js +47 -0
  87. package/src/utils/mock-interceptor.js +557 -0
  88. package/src/utils/oauth-utils.js +87 -0
  89. package/src/utils/request-utils.js +264 -0
  90. package/src/utils/response-utils.js +203 -0
  91. package/src/utils/sanitize.js +57 -0
  92. package/src/utils/schema-utils.js +1954 -0
  93. package/src/utils/slugger.js +187 -0
  94. package/src/utils/spec-parser.js +941 -0
  95. package/src/utils/url-utils.js +246 -0
  96. package/src/utils/xml-utils.js +61 -0
@@ -0,0 +1,514 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement, property } from "lit/decorators.js";
3
+ import { unsafeHTML } from "lit/directives/unsafe-html.js";
4
+ import { sanitizeHTML } from "../utils/sanitize.js";
5
+ import { marked } from "marked";
6
+ import { prepareResponseDefinitions } from "../utils/response-utils.js";
7
+ import { exampleLabel } from "~/utils/schema-utils";
8
+ import FontStyles from "~/styles/font-styles";
9
+ import FlexStyles from "~/styles/flex-styles";
10
+ import ParamListStyles from "~/styles/param-list-styles";
11
+ import InputStyles from "~/styles/input-styles";
12
+ import TabStyles from "~/styles/tab-styles";
13
+ import BorderStyles from "~/styles/border-styles";
14
+ import CustomStyles from "~/styles/custom-styles";
15
+ import "~/components/json-tree";
16
+ import "~/components/schema-tree";
17
+ import "~/components/schema-table";
18
+
19
+ @customElement("api-response")
20
+ export default class ApiResponse extends LitElement {
21
+ @property({ type: Object }) config: any;
22
+ @property({ type: String }) callback = "";
23
+ @property({ type: String }) webhook = "";
24
+ @property({ type: Object }) responses: any;
25
+ @property({ type: Object }) parser: any;
26
+ @property({ type: String, attribute: "schema-style" }) schemaStyle = "";
27
+ @property({ type: String, attribute: "render-style" }) renderStyle = "";
28
+ @property({ type: String, attribute: "selected-status" }) selectedStatus = "";
29
+ @property({ type: String, attribute: "selected-mime-type" }) selectedMimeType = "";
30
+ @property({ type: String, attribute: "active-schema-tab" }) activeSchemaTab = "schema";
31
+ @property({ type: Number, attribute: "schema-expand-level" }) schemaExpandLevel = 99999;
32
+ @property({ type: String, attribute: "schema-description-expanded" }) schemaDescriptionExpanded =
33
+ "false";
34
+ @property({ type: String, attribute: "allow-schema-description-expand-toggle" })
35
+ allowSchemaDescriptionExpandToggle = "false";
36
+ @property({ type: String, attribute: "schema-hide-read-only" }) schemaHideReadOnly = "false";
37
+ @property({ type: String, attribute: "schema-hide-write-only" }) schemaHideWriteOnly = "true";
38
+
39
+ defaultStatus = "";
40
+ defaultMimeType = "";
41
+ headersForEachRespStatus: any = {};
42
+ mimeResponsesForEachStatus: any = {};
43
+
44
+ constructor() {
45
+ super();
46
+ this.selectedStatus = "";
47
+ this.defaultStatus = "";
48
+ this.defaultMimeType = "";
49
+ this.headersForEachRespStatus = {};
50
+ this.mimeResponsesForEachStatus = {};
51
+ this.activeSchemaTab = "schema";
52
+ }
53
+
54
+ static get styles() {
55
+ return [
56
+ FontStyles,
57
+ FlexStyles,
58
+ TabStyles,
59
+ ParamListStyles,
60
+ InputStyles,
61
+ BorderStyles,
62
+ css`
63
+ .resp-headers-list {
64
+ margin-bottom: 16px;
65
+ }
66
+ :where(button, input[type='checkbox'], [tabindex='0']):focus-visible {
67
+ box-shadow: var(--focus-shadow);
68
+ }
69
+ :where(input[type='text'], input[type='password'], select, textarea):focus-visible {
70
+ border-color: var(--primary);
71
+ }
72
+ .resp-head {
73
+ vertical-align: middle;
74
+ padding: 16px 0 8px;
75
+
76
+ &.divider {
77
+ border-top: 1px solid var(--border);
78
+ margin-top: 10px;
79
+ }
80
+ }
81
+ .resp-status {
82
+ font-weight: bold;
83
+ font-size: calc(var(--font-size-small) + 1px);
84
+ }
85
+ .resp-descr {
86
+ font-size: var(--font-size-description);
87
+ color: var(--muted-foreground);
88
+ text-align: left;
89
+ }
90
+ .top-gap {
91
+ margin-top: 16px;
92
+ }
93
+ .example-panel {
94
+ font-size: var(--font-size-small);
95
+ margin: 0;
96
+ }
97
+ /* Response codes: a segmented control (one choice), the selected code tinted with the primary color */
98
+ .status-group {
99
+ display: inline-flex;
100
+ flex-wrap: wrap;
101
+ margin-top: 8px;
102
+ }
103
+ .status-btn {
104
+ margin: 0 0 0 -1px;
105
+ border-color: var(--input);
106
+ border-radius: 0;
107
+ background: color-mix(in srgb, var(--foreground) 5%, transparent);
108
+ color: var(--muted-foreground);
109
+
110
+ &:first-child {
111
+ margin-left: 0;
112
+ border-start-start-radius: var(--radius);
113
+ border-end-start-radius: var(--radius);
114
+ }
115
+ &:last-child {
116
+ border-start-end-radius: var(--radius);
117
+ border-end-end-radius: var(--radius);
118
+ }
119
+ &:hover {
120
+ background: var(--muted);
121
+ color: var(--foreground);
122
+ }
123
+ &[aria-pressed='true'] {
124
+ position: relative;
125
+ z-index: 1;
126
+ border-color: var(--primary);
127
+ background: color-mix(in srgb, var(--primary) 14%, transparent);
128
+ color: var(--primary);
129
+ }
130
+ &:focus-visible {
131
+ z-index: 2;
132
+ }
133
+ }
134
+ `,
135
+ CustomStyles,
136
+ ];
137
+ }
138
+
139
+ willUpdate(changedProperties) {
140
+ super.willUpdate?.(changedProperties);
141
+ // Sync configuration options passed down from parent <rapi-doc>
142
+ if (this.config && changedProperties.has("config")) {
143
+ if (this.config.renderStyle) this.renderStyle = this.config.renderStyle;
144
+ if (this.config.schemaStyle) this.schemaStyle = this.config.schemaStyle;
145
+ if (this.config.schemaExpandLevel !== undefined)
146
+ this.schemaExpandLevel = this.config.schemaExpandLevel;
147
+ if (this.config.schemaDescriptionExpanded !== undefined)
148
+ this.schemaDescriptionExpanded = this.config.schemaDescriptionExpanded;
149
+ if (this.config.allowSchemaDescriptionExpandToggle !== undefined)
150
+ this.allowSchemaDescriptionExpandToggle = this.config.allowSchemaDescriptionExpandToggle;
151
+ // In responses, readOnly fields are shown by default ('false') while writeOnly fields are hidden ('true').
152
+ // For webhooks, this behavior is inverted unless configured to 'never'.
153
+ if (this.config.schemaHideReadOnly !== undefined)
154
+ this.schemaHideReadOnly =
155
+ this.config.schemaHideReadOnly === "never"
156
+ ? "false"
157
+ : this.webhook === "true"
158
+ ? "true"
159
+ : "false";
160
+ if (this.config.schemaHideWriteOnly !== undefined)
161
+ this.schemaHideWriteOnly =
162
+ this.config.schemaHideWriteOnly === "never"
163
+ ? "false"
164
+ : this.webhook === "true"
165
+ ? "false"
166
+ : "true";
167
+ if (this.config.defaultSchemaTab) this.activeSchemaTab = this.config.defaultSchemaTab;
168
+ }
169
+ // Parse response definitions, headers, examples, and AST schemas when responses change
170
+ if (
171
+ changedProperties.has("responses") ||
172
+ changedProperties.has("callback") ||
173
+ changedProperties.has("webhook")
174
+ ) {
175
+ if (!this.responses || typeof this.responses !== "object") {
176
+ return;
177
+ }
178
+ const defs = prepareResponseDefinitions(this.responses, {
179
+ callback: this.callback,
180
+ webhook: this.webhook,
181
+ });
182
+ this.headersForEachRespStatus = defs.headersForEachRespStatus;
183
+ this.mimeResponsesForEachStatus = defs.mimeResponsesForEachStatus;
184
+ this.defaultStatus = defs.defaultStatus;
185
+ this.defaultMimeType = defs.defaultMimeType;
186
+ // Default to primary status code (e.g. '200') if none is currently selected or if the old status doesn't exist on this response
187
+ if (!this.selectedStatus || !this.responses?.[this.selectedStatus]) {
188
+ this.selectedStatus = defs.defaultStatus;
189
+ }
190
+ if (!this.selectedMimeType) {
191
+ this.selectedMimeType = defs.defaultMimeType;
192
+ }
193
+ }
194
+ }
195
+
196
+ render() {
197
+ return html`<div class="col regular-font response-panel ${this.renderStyle}-mode">
198
+ ${
199
+ this.callback === "true"
200
+ ? html`<h6 class="rapidoc-heading label-heading tiny-title" part="section-response-title heading heading-6">
201
+ Callback response
202
+ </h6>`
203
+ : html`<h4 class="rapidoc-heading label-heading req-res-title" part="section-response-title heading heading-4">Response</h4>`
204
+ }
205
+ <div>${this.responseTemplate()}</div>
206
+ </div> `;
207
+ }
208
+
209
+ // Called by rapi-doc after navigating in focused mode.
210
+ // In focused mode, the single <api-response> element instance is reused across operations.
211
+ // We reset selections back to the default status code and MIME type of the active endpoint,
212
+ // preventing user selections from a prior endpoint (e.g., status 400) from carrying over or hiding panels.
213
+ resetSelection() {
214
+ this.selectedStatus =
215
+ this.defaultStatus ||
216
+ (this.responses ? Object.keys(this.responses).find((k) => k !== "$$ref") || "" : "");
217
+ if (this.selectedStatus && this.mimeResponsesForEachStatus?.[this.selectedStatus]) {
218
+ this.selectedMimeType =
219
+ Object.keys(this.mimeResponsesForEachStatus[this.selectedStatus])[0] || "";
220
+ } else {
221
+ this.selectedMimeType = this.defaultMimeType || "";
222
+ }
223
+ }
224
+
225
+ responseTemplate() {
226
+ if (!this.responses) {
227
+ return "";
228
+ }
229
+ // Fallback guard: Ensure selectedStatus and selectedMimeType always point to valid options
230
+ // for this endpoint, even if resetSelection was called or responses have not re-rendered yet.
231
+ if (!this.selectedStatus || !this.responses[this.selectedStatus]) {
232
+ this.selectedStatus =
233
+ this.defaultStatus ||
234
+ Object.keys(this.responses).find((k) => k !== "$$ref") ||
235
+ Object.keys(this.responses)[0] ||
236
+ "";
237
+ }
238
+ if (
239
+ !this.selectedMimeType &&
240
+ this.selectedStatus &&
241
+ this.mimeResponsesForEachStatus?.[this.selectedStatus]
242
+ ) {
243
+ this.selectedMimeType =
244
+ Object.keys(this.mimeResponsesForEachStatus[this.selectedStatus])[0] ||
245
+ this.defaultMimeType ||
246
+ "";
247
+ }
248
+ return html`
249
+ ${
250
+ Object.keys(this.responses).length > 1
251
+ ? html`<div class="status-group" role="group" aria-label="Response status">
252
+ ${Object.keys(this.responses).map(
253
+ (respStatus) =>
254
+ html` ${
255
+ respStatus === "$$ref" // Ignore '$$ref' if present from legacy reference parsing
256
+ ? ""
257
+ : html`<button
258
+ @click="${() => {
259
+ this.selectedStatus = respStatus;
260
+ if (
261
+ this.responses[respStatus].content &&
262
+ Object.keys(this.responses[respStatus].content)[0]
263
+ ) {
264
+ this.selectedMimeType = Object.keys(
265
+ this.responses[respStatus].content,
266
+ )[0];
267
+ } else {
268
+ this.selectedMimeType = undefined;
269
+ }
270
+ }}"
271
+ class="btn btn-sm status-btn"
272
+ aria-pressed="${this.selectedStatus === respStatus ? "true" : "false"}"
273
+ part="btn ${
274
+ this.selectedStatus === respStatus
275
+ ? "btn-response-status btn-selected-response-status"
276
+ : " btn-response-status"
277
+ }"
278
+ >
279
+ ${respStatus}
280
+ </button>`
281
+ }`,
282
+ )}
283
+ </div>`
284
+ : html`<span>${Object.keys(this.responses)[0]}</span>`
285
+ }
286
+ ${Object.keys(this.responses).map(
287
+ (status) =>
288
+ html`<div style="display: ${status === this.selectedStatus ? "block" : "none"}">
289
+ <div class="top-gap">
290
+ <span class="resp-descr m-markdown">${unsafeHTML(sanitizeHTML(marked(this.responses[status]?.description || "")))}</span>
291
+ ${
292
+ this.headersForEachRespStatus[status] &&
293
+ this.headersForEachRespStatus[status]?.length > 0
294
+ ? html`${this.responseHeaderListTemplate(this.headersForEachRespStatus[status])}`
295
+ : ""
296
+ }
297
+ </div>
298
+ ${
299
+ !this.mimeResponsesForEachStatus[status] ||
300
+ Object.keys(this.mimeResponsesForEachStatus[status]).length === 0
301
+ ? ""
302
+ : html`<div part="tab-panel" class="tab-panel col">
303
+ <div
304
+ part="tab-btn-row"
305
+ class="tab-buttons row"
306
+ @click="${(e) => {
307
+ if (e.target.tagName.toLowerCase() === "button") {
308
+ this.activeSchemaTab = e.target.dataset.tab;
309
+ }
310
+ }}"
311
+ >
312
+ <button part="tab-btn" class="tab-btn ${this.activeSchemaTab === "example" ? "active" : ""}" data-tab="example">
313
+ EXAMPLE
314
+ </button>
315
+ <button part="tab-btn" class="tab-btn ${this.activeSchemaTab !== "example" ? "active" : ""}" data-tab="schema">
316
+ SCHEMA
317
+ </button>
318
+ ${
319
+ Object.keys(this.mimeResponsesForEachStatus[status]).length === 1
320
+ ? html`<span class="tab-bar-end">${Object.keys(this.mimeResponsesForEachStatus[status])[0]}</span>`
321
+ : html`${this.mimeTypeDropdownTemplate(Object.keys(this.mimeResponsesForEachStatus[status]))}`
322
+ }
323
+ </div>
324
+ ${
325
+ this.activeSchemaTab === "example"
326
+ ? html`<div part="tab-content" class="tab-content col">
327
+ ${this.mimeExampleTemplate(this.mimeResponsesForEachStatus[status][this.selectedMimeType])}
328
+ </div>`
329
+ : html`<div part="tab-content" class="tab-content col">
330
+ ${this.mimeSchemaTemplate(this.mimeResponsesForEachStatus[status][this.selectedMimeType])}
331
+ </div>`
332
+ }
333
+ </div> `
334
+ }
335
+ </div>`,
336
+ )}
337
+ `;
338
+ }
339
+
340
+ responseHeaderListTemplate(respHeaders) {
341
+ return html`<h5 class="rapidoc-heading label-heading resp-headers" part="heading heading-5">Response headers</h5>
342
+ <div class="param-list-container resp-headers-list">
343
+ <ul class="param-list" part="param-list" role="list">
344
+ ${respHeaders.map(
345
+ (v) =>
346
+ html`<li class="param" part="param">
347
+ <div class="param-label" part="param-label">
348
+ <div class="param-name" part="param-name">${v.name || ""}</div>
349
+ ${v.schema?.type ? html`<div class="param-type" part="param-type">${v.schema.type}</div>` : ""}
350
+ </div>
351
+ ${
352
+ v.schema?.example
353
+ ? html`<dl class="param-constraint" part="param-constraint">
354
+ <dt class="param-constraint-label" part="param-constraint-label">Example:</dt>
355
+ <dd>${v.schema.example}</dd>
356
+ </dl>`
357
+ : ""
358
+ }
359
+ <div class="param-details" part="param-details">
360
+ ${v.description ? html`<div class="m-markdown-small">${unsafeHTML(sanitizeHTML(marked(v.description)))}</div>` : ""}
361
+ </div>
362
+ </li>`,
363
+ )}
364
+ </ul>
365
+ </div>`;
366
+ }
367
+
368
+ mimeTypeDropdownTemplate(mimeTypes) {
369
+ return html`<select
370
+ aria-label="mime types"
371
+ class="select-sm tab-bar-end"
372
+ @change="${(e) => {
373
+ this.selectedMimeType = e.target.value;
374
+ }}"
375
+ >
376
+ ${mimeTypes.map(
377
+ (mimeType) =>
378
+ html`<option value="${mimeType}" ?selected="${mimeType === this.selectedMimeType}">${mimeType}</option>`,
379
+ )}
380
+ </select>`;
381
+ }
382
+
383
+ onSelectExample(e) {
384
+ const exampleContainerEl = e.target.closest(".example-panel");
385
+ const exampleEls = [...exampleContainerEl.querySelectorAll(".example")];
386
+
387
+ exampleEls.forEach((v) => {
388
+ v.style.display = v.dataset.example === e.target.value ? "block" : "none";
389
+ });
390
+ }
391
+
392
+ mimeExampleTemplate(mimeRespDetails) {
393
+ if (!mimeRespDetails || !mimeRespDetails.examples || mimeRespDetails.examples.length === 0) {
394
+ return html` <pre style="color:var(--destructive)" class="example-panel"> No example provided </pre> `;
395
+ }
396
+ return html`
397
+ ${
398
+ mimeRespDetails.examples.length === 1
399
+ ? html` ${
400
+ mimeRespDetails.examples[0].exampleFormat === "json"
401
+ ? html` ${
402
+ mimeRespDetails.examples[0].exampleSummary &&
403
+ mimeRespDetails.examples[0].exampleSummary.length > 80
404
+ ? html`<div style="padding: 4px 0">${mimeRespDetails.examples[0].exampleSummary}</div>`
405
+ : ""
406
+ }
407
+ ${
408
+ mimeRespDetails.examples[0].exampleDescription
409
+ ? html`<div class="m-markdown-small" style="padding: 4px 0">
410
+ ${unsafeHTML(sanitizeHTML(marked(mimeRespDetails.examples[0].exampleDescription || "")))}
411
+ </div>`
412
+ : ""
413
+ }
414
+ <json-tree
415
+ render-style="${this.renderStyle}"
416
+ .data="${mimeRespDetails.examples[0].exampleValue}"
417
+ class="example-panel"
418
+ exportparts="btn:btn, btn-outline:btn-outline, btn-copy:btn-copy"
419
+ ></json-tree>`
420
+ : html`
421
+ ${
422
+ mimeRespDetails.examples[0].exampleSummary &&
423
+ mimeRespDetails.examples[0].exampleSummary.length > 80
424
+ ? html`<div style="padding: 4px 0">${mimeRespDetails.examples[0].exampleSummary}</div>`
425
+ : ""
426
+ }
427
+ ${
428
+ mimeRespDetails.examples[0].exampleDescription
429
+ ? html`<div class="m-markdown-small" style="padding: 4px 0">
430
+ ${unsafeHTML(sanitizeHTML(marked(mimeRespDetails.examples[0].exampleDescription || "")))}
431
+ </div>`
432
+ : ""
433
+ }
434
+ <pre class="example-panel">${mimeRespDetails.examples[0].exampleValue}</pre>
435
+ `
436
+ }`
437
+ : html`
438
+ <div class="example-panel">
439
+ <select
440
+ aria-label="response examples"
441
+ class="select-sm example-select"
442
+ title="${exampleLabel(mimeRespDetails.examples.find((v) => v.exampleId === mimeRespDetails.selectedExample))}"
443
+ @change="${(e) => this.onSelectExample(e)}"
444
+ >
445
+ ${mimeRespDetails.examples.map(
446
+ (v) =>
447
+ html`<option value="${v.exampleId}" title="${exampleLabel(v)}" ?selected=${v.exampleId === mimeRespDetails.selectedExample}>
448
+ ${exampleLabel(v)}
449
+ </option>`,
450
+ )}
451
+ </select>
452
+ ${mimeRespDetails.examples.map(
453
+ (v) => html`
454
+ <div
455
+ class="example"
456
+ data-example="${v.exampleId}"
457
+ style="display: ${v.exampleId === mimeRespDetails.selectedExample ? "block" : "none"}"
458
+ >
459
+ ${
460
+ v.exampleDescription
461
+ ? html`<div class="m-markdown-small" style="padding: 4px 0">
462
+ ${unsafeHTML(sanitizeHTML(marked(v.exampleDescription || "")))}
463
+ </div>`
464
+ : ""
465
+ }
466
+ ${
467
+ v.exampleFormat === "json"
468
+ ? html`<json-tree
469
+ render-style="${this.renderStyle}"
470
+ .data="${v.exampleValue}"
471
+ exportparts="btn:btn, btn-outline:btn-outline, btn-copy:btn-copy"
472
+ ></json-tree>`
473
+ : html`<pre>${v.exampleValue}</pre>`
474
+ }
475
+ </div>
476
+ `,
477
+ )}
478
+ </div>
479
+ `
480
+ }
481
+ `;
482
+ }
483
+
484
+ mimeSchemaTemplate(mimeRespDetails) {
485
+ if (!mimeRespDetails) {
486
+ return html` <pre style="color:var(--destructive)" class="example-panel"> Schema not found</pre> `;
487
+ }
488
+ return html` ${
489
+ this.schemaStyle === "table"
490
+ ? html`
491
+ <schema-table
492
+ .data="${mimeRespDetails.schemaTree}"
493
+ schema-expand-level="${this.schemaExpandLevel}"
494
+ schema-description-expanded="${this.schemaDescriptionExpanded}"
495
+ allow-schema-description-expand-toggle="${this.allowSchemaDescriptionExpandToggle}"
496
+ schema-hide-read-only="${this.schemaHideReadOnly}"
497
+ schema-hide-write-only="${this.schemaHideWriteOnly}"
498
+ exportparts="schema-description:schema-description, schema-multiline-toggle:schema-multiline-toggle"
499
+ >
500
+ </schema-table>
501
+ `
502
+ : html`<schema-tree
503
+ .data="${mimeRespDetails.schemaTree}"
504
+ schema-expand-level="${this.schemaExpandLevel}"
505
+ schema-description-expanded="${this.schemaDescriptionExpanded}"
506
+ allow-schema-description-expand-toggle="${this.allowSchemaDescriptionExpandToggle}"
507
+ schema-hide-read-only="${this.schemaHideReadOnly}"
508
+ schema-hide-write-only="${this.schemaHideWriteOnly}"
509
+ exportparts="schema-description:schema-description, schema-multiline-toggle:schema-multiline-toggle"
510
+ >
511
+ </schema-tree>`
512
+ }`;
513
+ }
514
+ }
@@ -0,0 +1,171 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement, property } from "lit/decorators.js";
3
+ import { copyToClipboard } from "~/utils/common-utils";
4
+ import FontStyles from "~/styles/font-styles";
5
+ import BorderStyles from "~/styles/border-styles";
6
+ import InputStyles from "~/styles/input-styles";
7
+ import CustomStyles from "~/styles/custom-styles";
8
+
9
+ @customElement("json-tree")
10
+ export default class JsonTree extends LitElement {
11
+ @property({ type: Object }) data = null;
12
+ @property({ type: String, attribute: "render-style" }) renderStyle = "";
13
+
14
+ static get styles() {
15
+ return [
16
+ FontStyles,
17
+ BorderStyles,
18
+ InputStyles,
19
+ css`
20
+ :host {
21
+ display: flex;
22
+ }
23
+ :where(button, input[type='checkbox'], [tabindex='0']):focus-visible {
24
+ box-shadow: var(--focus-shadow);
25
+ }
26
+ :where(input[type='text'], input[type='password'], select, textarea):focus-visible {
27
+ border-color: var(--primary);
28
+ }
29
+ .json-tree {
30
+ position: relative;
31
+ font-family: var(--font-mono);
32
+ font-size: var(--font-size-small);
33
+ display: inline-block;
34
+ overflow: hidden;
35
+ word-break: break-all;
36
+ flex: 1;
37
+ line-height: calc(var(--font-size-small) + 6px);
38
+ min-height: 40px;
39
+ direction: ltr;
40
+ text-align: left;
41
+ }
42
+
43
+ .open-bracket {
44
+ display: inline-block;
45
+ padding: 0 20px 0 0;
46
+ cursor: pointer;
47
+ border: 1px solid transparent;
48
+ border-radius: min(var(--radius), 0.5rem);
49
+
50
+ &:hover {
51
+ color: var(--primary);
52
+ border: 1px solid var(--border);
53
+ }
54
+
55
+ &.expanded:hover ~ .inside-bracket {
56
+ border-left: 1px solid var(--muted-foreground);
57
+ }
58
+ &.expanded:hover ~ .close-bracket {
59
+ color: var(--primary);
60
+ }
61
+ &.collapsed + .inside-bracket,
62
+ &.collapsed + .inside-bracket + .close-bracket {
63
+ display: none;
64
+ }
65
+ &.expanded .open-bracket-collapsed {
66
+ display: none;
67
+ }
68
+ &.collapsed .open-bracket-expanded {
69
+ display: none;
70
+ }
71
+ }
72
+ .close-bracket {
73
+ border: 1px solid transparent;
74
+ border-radius: min(var(--radius), 0.5rem);
75
+ display: inline-block;
76
+ }
77
+ .inside-bracket {
78
+ padding-left: 12px;
79
+ overflow: hidden;
80
+ border-left: 1px dotted var(--border);
81
+ }
82
+
83
+ .string {
84
+ color: var(--green);
85
+ }
86
+ .number {
87
+ color: var(--blue);
88
+ }
89
+ .null {
90
+ color: var(--destructive);
91
+ }
92
+ .boolean {
93
+ color: var(--purple);
94
+ }
95
+ .object {
96
+ color: var(--foreground);
97
+ }
98
+ .toolbar {
99
+ position: absolute;
100
+ top: 5px;
101
+ right: 6px;
102
+ display: flex;
103
+ padding: 2px;
104
+ align-items: center;
105
+ }
106
+ `,
107
+ CustomStyles,
108
+ ];
109
+ }
110
+
111
+ render() {
112
+ return html`
113
+ <div
114
+ class="json-tree"
115
+ @click="${(e) => {
116
+ if (e.target.classList.contains("btn-copy")) {
117
+ copyToClipboard(JSON.stringify(this.data, null, 2), e);
118
+ } else {
119
+ this.toggleExpand(e);
120
+ }
121
+ }}"
122
+ >
123
+ <div class="toolbar">
124
+ <button class="btn btn-sm btn-outline toolbar-btn btn-copy" part="btn btn-outline btn-copy">Copy</button>
125
+ </div>
126
+ ${this.generateTree(this.data, true)}
127
+ </div>
128
+ `;
129
+ }
130
+
131
+ generateTree(data, isLast = false) {
132
+ if (data === null) {
133
+ return html`<span class="null">null</span>${isLast ? "" : ","}`;
134
+ }
135
+ if (typeof data === "object" && data instanceof Date === false) {
136
+ const detailType = Array.isArray(data) ? "array" : "pure_object";
137
+ if (Object.keys(data).length === 0) {
138
+ return html`${Array.isArray(data) ? "[ ]," : "{ },"}`;
139
+ }
140
+ return html`
141
+ <div class="open-bracket expanded ${detailType === "array" ? "array" : "object"}">
142
+ <span class="open-bracket-expanded">${detailType === "array" ? "[" : "{"}</span>
143
+ <span class="open-bracket-collapsed">${detailType === "array" ? "[...]" : "{...}"}</span>
144
+ </div>
145
+ <div class="inside-bracket">
146
+ ${Object.keys(data).map(
147
+ (key, i, a) =>
148
+ html`<div class="item">
149
+ ${detailType === "pure_object" ? html`"${key}":` : ""} ${this.generateTree(data[key], i === a.length - 1)}
150
+ </div>`,
151
+ )}
152
+ </div>
153
+ <div class="close-bracket">${detailType === "array" ? "]" : "}"}${isLast ? "" : ","}</div>
154
+ `;
155
+ }
156
+ return typeof data === "string" || data instanceof Date
157
+ ? html`<span class="${typeof data}">"${data}"</span>${isLast ? "" : ","}`
158
+ : html`<span class="${typeof data}">${data}</span>${isLast ? "" : ","}`;
159
+ }
160
+
161
+ toggleExpand(e) {
162
+ const openBracketEl = e.target.closest(".open-bracket");
163
+ if (openBracketEl) {
164
+ if (openBracketEl.classList.contains("expanded")) {
165
+ openBracketEl.classList.replace("expanded", "collapsed");
166
+ } else {
167
+ openBracketEl.classList.replace("collapsed", "expanded");
168
+ }
169
+ }
170
+ }
171
+ }