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