@prauga/flexdoc-backend 2.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 (53) hide show
  1. package/README.md +227 -0
  2. package/dist/flexdoc.module.d.ts +14 -0
  3. package/dist/flexdoc.module.js +77 -0
  4. package/dist/flexdoc.module.js.map +1 -0
  5. package/dist/flexdoc.module.test.d.ts +1 -0
  6. package/dist/flexdoc.module.test.js +72 -0
  7. package/dist/flexdoc.module.test.js.map +1 -0
  8. package/dist/flexdoc.service.d.ts +5 -0
  9. package/dist/flexdoc.service.js +24 -0
  10. package/dist/flexdoc.service.js.map +1 -0
  11. package/dist/framework-adapters.d.ts +25 -0
  12. package/dist/framework-adapters.js +163 -0
  13. package/dist/framework-adapters.js.map +1 -0
  14. package/dist/framework-adapters.test.d.ts +1 -0
  15. package/dist/framework-adapters.test.js +37 -0
  16. package/dist/framework-adapters.test.js.map +1 -0
  17. package/dist/index.d.ts +6 -0
  18. package/dist/index.js +14 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/interfaces.d.ts +169 -0
  21. package/dist/interfaces.js +3 -0
  22. package/dist/interfaces.js.map +1 -0
  23. package/dist/renderer/flexdoc.standalone.css +1 -0
  24. package/dist/renderer/flexdoc.standalone.js +200 -0
  25. package/dist/renderer-assets.d.ts +5 -0
  26. package/dist/renderer-assets.js +60 -0
  27. package/dist/renderer-assets.js.map +1 -0
  28. package/dist/scripts/generate-auth.d.ts +2 -0
  29. package/dist/scripts/generate-auth.js +107 -0
  30. package/dist/scripts/generate-auth.js.map +1 -0
  31. package/dist/setup.d.ts +6 -0
  32. package/dist/setup.js +220 -0
  33. package/dist/setup.js.map +1 -0
  34. package/dist/setup.test.d.ts +1 -0
  35. package/dist/setup.test.js +89 -0
  36. package/dist/setup.test.js.map +1 -0
  37. package/dist/template.d.ts +15 -0
  38. package/dist/template.js +64 -0
  39. package/dist/template.js.map +1 -0
  40. package/dist/template.test.d.ts +1 -0
  41. package/dist/template.test.js +38 -0
  42. package/dist/template.test.js.map +1 -0
  43. package/dist/templates/code-examples.njk +106 -0
  44. package/dist/templates/content.njk +272 -0
  45. package/dist/templates/footer.njk +38 -0
  46. package/dist/templates/header.njk +57 -0
  47. package/dist/templates/layout.njk +647 -0
  48. package/dist/templates/parameters.njk +64 -0
  49. package/dist/templates/request-body.njk +63 -0
  50. package/dist/templates/responses.njk +78 -0
  51. package/dist/templates/sidebar.njk +117 -0
  52. package/dist/tsconfig.tsbuildinfo +1 -0
  53. package/package.json +34 -0
@@ -0,0 +1,647 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="{{ themeMode }}">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{ title }}</title>
7
+ <meta name="description" content="{{ description }}">
8
+ {% if favicon %}
9
+ <link rel="icon" href="{{ favicon }}">
10
+ {% endif %}
11
+
12
+ <!-- Tailwind CSS -->
13
+ <script src="https://cdn.tailwindcss.com"></script>
14
+ <script>
15
+ tailwind.config = {
16
+ darkMode: 'class',
17
+ theme: {
18
+ extend: {
19
+ colors: {
20
+ primary: {
21
+ 50: '{{ themeConfig.colors.primary.light | default("#eff6ff") }}',
22
+ 100: '#dbeafe',
23
+ 200: '#bfdbfe',
24
+ 300: '#93c5fd',
25
+ 400: '#60a5fa',
26
+ 500: '{{ themeConfig.colors.primary.main | default("#3b82f6") }}',
27
+ 600: '{{ themeConfig.colors.primary.dark | default("#2563eb") }}',
28
+ 700: '#1d4ed8',
29
+ 800: '#1e40af',
30
+ 900: '#1e3a8a',
31
+ },
32
+ success: {
33
+ 500: '{{ themeConfig.colors.success.main | default("#10b981") }}',
34
+ 600: '{{ themeConfig.colors.success.dark | default("#059669") }}',
35
+ },
36
+ error: {
37
+ 500: '{{ themeConfig.colors.error.main | default("#ef4444") }}',
38
+ 600: '{{ themeConfig.colors.error.dark | default("#dc2626") }}',
39
+ }
40
+ },
41
+ fontFamily: {
42
+ sans: ['{{ themeConfig.typography.fontFamily | default("Inter, system-ui, sans-serif") }}'],
43
+ mono: ['{{ themeConfig.typography.code.fontFamily | default("Monaco, Menlo, monospace") }}'],
44
+ },
45
+ fontSize: {
46
+ base: '{{ themeConfig.typography.fontSize | default("14px") }}',
47
+ }
48
+ }
49
+ }
50
+ }
51
+ </script>
52
+
53
+ <!-- Lucide Icons -->
54
+ <script src="https://unpkg.com/lucide@latest"></script>
55
+
56
+ <!-- Custom Styles -->
57
+ <style>
58
+ :root {
59
+ /* Primary Colors */
60
+ --flexdoc-primary: {{ themeConfig.colors.primary.main | default('#3b82f6') }};
61
+ --flexdoc-primary-light: {{ themeConfig.colors.primary.light | default('#60a5fa') }};
62
+ --flexdoc-primary-dark: {{ themeConfig.colors.primary.dark | default('#2563eb') }};
63
+
64
+ /* Success Colors */
65
+ --flexdoc-success: {{ themeConfig.colors.success.main | default('#10b981') }};
66
+ --flexdoc-success-light: {{ themeConfig.colors.success.light | default('#34d399') }};
67
+ --flexdoc-success-dark: {{ themeConfig.colors.success.dark | default('#059669') }};
68
+
69
+ /* Error Colors */
70
+ --flexdoc-error: {{ themeConfig.colors.error.main | default('#ef4444') }};
71
+ --flexdoc-error-light: {{ themeConfig.colors.error.light | default('#f87171') }};
72
+ --flexdoc-error-dark: {{ themeConfig.colors.error.dark | default('#dc2626') }};
73
+
74
+ /* Text Colors */
75
+ --flexdoc-text-primary: {{ themeConfig.colors.text.primary | default('#111827') }};
76
+ --flexdoc-text-secondary: {{ themeConfig.colors.text.secondary | default('#6b7280') }};
77
+
78
+ /* Gray Colors */
79
+ --flexdoc-gray-50: {{ themeConfig.colors.gray[50] | default('#f9fafb') }};
80
+ --flexdoc-gray-100: {{ themeConfig.colors.gray[100] | default('#f3f4f6') }};
81
+
82
+ /* Border Colors */
83
+ --flexdoc-border-light: {{ themeConfig.colors.border.light | default('#e5e7eb') }};
84
+ --flexdoc-border-dark: {{ themeConfig.colors.border.dark | default('#374151') }};
85
+
86
+ /* Typography */
87
+ --flexdoc-font-size: {{ themeConfig.typography.fontSize | default('14px') }};
88
+ --flexdoc-line-height: {{ themeConfig.typography.lineHeight | default('1.5') }};
89
+ --flexdoc-font-family: {{ themeConfig.typography.fontFamily | default('Inter, system-ui, sans-serif') }};
90
+
91
+ /* Headings */
92
+ --flexdoc-headings-font-family: {{ themeConfig.typography.headings.fontFamily | default('inherit') }};
93
+ --flexdoc-headings-font-weight: {{ themeConfig.typography.headings.fontWeight | default('600') }};
94
+
95
+ /* Code */
96
+ --flexdoc-code-font-size: {{ themeConfig.typography.code.fontSize | default('13px') }};
97
+ --flexdoc-code-font-family: {{ themeConfig.typography.code.fontFamily | default('Monaco, Menlo, monospace') }};
98
+ --flexdoc-code-line-height: {{ themeConfig.typography.code.lineHeight | default('1.4') }};
99
+ --flexdoc-code-color: {{ themeConfig.typography.code.colorLight | default('#374151') }};
100
+ --flexdoc-code-bg: {{ themeConfig.typography.code.backgroundColorLight | default('#f3f4f6') }};
101
+
102
+ /* Sidebar */
103
+ --flexdoc-sidebar-bg: {{ themeConfig.sidebar.backgroundColor | default('#ffffff') }};
104
+ --flexdoc-sidebar-text: {{ themeConfig.sidebar.textColor | default('#374151') }};
105
+ --flexdoc-sidebar-active: {{ themeConfig.sidebar.activeTextColor | default('#3b82f6') }};
106
+ --flexdoc-sidebar-border: {{ themeConfig.sidebar.borderColor | default('#e5e7eb') }};
107
+
108
+ /* Method Colors */
109
+ --flexdoc-method-get-bg: {{ themeConfig.methodColors.get.bg | default('#dbeafe') }};
110
+ --flexdoc-method-get-border: {{ themeConfig.methodColors.get.border | default('#93c5fd') }};
111
+ --flexdoc-method-post-bg: {{ themeConfig.methodColors.post.bg | default('#d1fae5') }};
112
+ --flexdoc-method-post-border: {{ themeConfig.methodColors.post.border | default('#6ee7b7') }};
113
+ --flexdoc-method-put-bg: {{ themeConfig.methodColors.put.bg | default('#fed7aa') }};
114
+ --flexdoc-method-put-border: {{ themeConfig.methodColors.put.border | default('#fdba74') }};
115
+ --flexdoc-method-delete-bg: {{ themeConfig.methodColors.delete.bg | default('#fecaca') }};
116
+ --flexdoc-method-delete-border: {{ themeConfig.methodColors.delete.border | default('#f87171') }};
117
+ --flexdoc-method-patch-bg: {{ themeConfig.methodColors.patch.bg | default('#e9d5ff') }};
118
+ --flexdoc-method-patch-border: {{ themeConfig.methodColors.patch.border | default('#c4b5fd') }};
119
+ --flexdoc-method-options-bg: {{ themeConfig.methodColors.options.bg | default('#f3f4f6') }};
120
+ --flexdoc-method-options-border: {{ themeConfig.methodColors.options.border | default('#d1d5db') }};
121
+ }
122
+
123
+ .dark {
124
+ /* Dark mode overrides */
125
+ --flexdoc-sidebar-bg: {{ themeConfig.sidebar.backgroundColorDark | default('#1f2937') }};
126
+ --flexdoc-sidebar-text: {{ themeConfig.sidebar.textColorDark | default('#d1d5db') }};
127
+ --flexdoc-sidebar-active: {{ themeConfig.sidebar.activeTextColorDark | default('#60a5fa') }};
128
+ --flexdoc-sidebar-border: {{ themeConfig.sidebar.borderColorDark | default('#374151') }};
129
+
130
+ /* Code dark mode overrides */
131
+ --flexdoc-code-color: {{ themeConfig.typography.code.color | default('#e5e7eb') }};
132
+ --flexdoc-code-bg: {{ themeConfig.typography.code.backgroundColor | default('#2d3748') }};
133
+ }
134
+
135
+ /* Apply custom variables */
136
+ body {
137
+ font-family: var(--flexdoc-font-family);
138
+ font-size: var(--flexdoc-font-size);
139
+ line-height: var(--flexdoc-line-height);
140
+ }
141
+
142
+ h1, h2, h3, h4, h5, h6 {
143
+ font-family: var(--flexdoc-headings-font-family);
144
+ font-weight: var(--flexdoc-headings-font-weight);
145
+ }
146
+
147
+ code, pre {
148
+ font-family: var(--flexdoc-code-font-family);
149
+ font-size: var(--flexdoc-code-font-size);
150
+ line-height: var(--flexdoc-code-line-height);
151
+ }
152
+
153
+ /* Smooth transitions */
154
+ * {
155
+ transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
156
+ }
157
+
158
+ /* Custom scrollbar */
159
+ ::-webkit-scrollbar {
160
+ width: 6px;
161
+ height: 6px;
162
+ }
163
+
164
+ ::-webkit-scrollbar-track {
165
+ background: transparent;
166
+ }
167
+
168
+ ::-webkit-scrollbar-thumb {
169
+ background: rgba(156, 163, 175, 0.5);
170
+ border-radius: 3px;
171
+ }
172
+
173
+ ::-webkit-scrollbar-thumb:hover {
174
+ background: rgba(156, 163, 175, 0.7);
175
+ }
176
+
177
+ .dark ::-webkit-scrollbar-thumb {
178
+ background: rgba(75, 85, 99, 0.5);
179
+ }
180
+
181
+ .dark ::-webkit-scrollbar-thumb:hover {
182
+ background: rgba(75, 85, 99, 0.7);
183
+ }
184
+
185
+ /* Code block styling */
186
+ .code-block {
187
+ background: var(--flexdoc-code-bg);
188
+ border-radius: 8px;
189
+ overflow: hidden;
190
+ border: 1px solid rgba(0, 0, 0, 0.05);
191
+ }
192
+
193
+ .dark .code-block {
194
+ border-color: transparent;
195
+ }
196
+
197
+ .code-block pre {
198
+ margin: 0;
199
+ padding: 1rem;
200
+ overflow-x: auto;
201
+ color: var(--flexdoc-code-color);
202
+ }
203
+
204
+ /* Method badges using custom colors */
205
+ .method-get {
206
+ background-color: var(--flexdoc-method-get-bg);
207
+ border-color: var(--flexdoc-method-get-border);
208
+ color: #1e40af;
209
+ }
210
+ .method-post {
211
+ background-color: var(--flexdoc-method-post-bg);
212
+ border-color: var(--flexdoc-method-post-border);
213
+ color: #065f46;
214
+ }
215
+ .method-put {
216
+ background-color: var(--flexdoc-method-put-bg);
217
+ border-color: var(--flexdoc-method-put-border);
218
+ color: #9a3412;
219
+ }
220
+ .method-delete {
221
+ background-color: var(--flexdoc-method-delete-bg);
222
+ border-color: var(--flexdoc-method-delete-border);
223
+ color: #991b1b;
224
+ }
225
+ .method-patch {
226
+ background-color: var(--flexdoc-method-patch-bg);
227
+ border-color: var(--flexdoc-method-patch-border);
228
+ color: #581c87;
229
+ }
230
+ .method-options {
231
+ background-color: var(--flexdoc-method-options-bg);
232
+ border-color: var(--flexdoc-method-options-border);
233
+ color: #4b5563;
234
+ }
235
+ .method-head {
236
+ background-color: #f3f4f6; /* gray-100 */
237
+ color: #1f2937; /* gray-800 */
238
+ border-color: #e5e7eb; /* gray-200 */
239
+ }
240
+ .dark .method-head {
241
+ background-color: #374151; /* gray-700 */
242
+ color: #e5e7eb; /* gray-200 */
243
+ border-color: #4b5563; /* gray-600 */
244
+ }
245
+
246
+ /* Status code badges (no @apply; works with CDN) */
247
+ .status-2xx {
248
+ background-color: #dcfce7; /* green-100 */
249
+ color: #166534; /* green-800 */
250
+ border-color: #bbf7d0; /* green-200 */
251
+ }
252
+ .dark .status-2xx {
253
+ background-color: #14532d; /* green-900 */
254
+ color: #bbf7d0; /* green-200 */
255
+ }
256
+ .status-3xx {
257
+ background-color: #dbeafe; /* blue-100 */
258
+ color: #1e40af; /* blue-800 */
259
+ border-color: #bfdbfe; /* blue-200 */
260
+ }
261
+ .dark .status-3xx {
262
+ background-color: #1e3a8a; /* blue-900 */
263
+ color: #bfdbfe; /* blue-200 */
264
+ }
265
+ .status-4xx {
266
+ background-color: #ffedd5; /* orange-100 */
267
+ color: #9a3412; /* orange-800 */
268
+ border-color: #fed7aa; /* orange-200 */
269
+ }
270
+ .dark .status-4xx {
271
+ background-color: #7c2d12; /* orange-900 */
272
+ color: #fed7aa; /* orange-200 */
273
+ }
274
+ .status-5xx {
275
+ background-color: #fee2e2; /* red-100 */
276
+ color: #991b1b; /* red-800 */
277
+ border-color: #fecaca; /* red-200 */
278
+ }
279
+ .dark .status-5xx {
280
+ background-color: #7f1d1d; /* red-900 */
281
+ color: #fecaca; /* red-200 */
282
+ }
283
+
284
+ /* Hover effects */
285
+ .hover-lift {
286
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
287
+ }
288
+
289
+ .hover-lift:hover {
290
+ transform: translateY(-1px);
291
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
292
+ }
293
+
294
+ .dark .hover-lift:hover {
295
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3), 0 10px 10px -5px rgba(0, 0, 0, 0.2);
296
+ }
297
+
298
+ /* Animation for collapsible sections */
299
+ .collapsible-content {
300
+ max-height: 0;
301
+ overflow: hidden;
302
+ transition: max-height 0.3s ease;
303
+ }
304
+
305
+ .collapsible-content.expanded {
306
+ max-height: 2000px;
307
+ }
308
+
309
+ /* Sidebar styling */
310
+ .sidebar {
311
+ background-color: var(--flexdoc-sidebar-bg);
312
+ color: var(--flexdoc-sidebar-text);
313
+ border-color: var(--flexdoc-sidebar-border);
314
+ }
315
+
316
+ .sidebar .endpoint-item.active {
317
+ color: var(--flexdoc-sidebar-active);
318
+ background-color: rgba(59, 130, 246, 0.1);
319
+ }
320
+
321
+ /* Hide all endpoint details by default */
322
+ .endpoint-detail {
323
+ display: none;
324
+ }
325
+
326
+ .endpoint-detail.active {
327
+ display: block;
328
+ }
329
+
330
+ /* Overview is shown by default */
331
+ .overview-content {
332
+ display: block;
333
+ }
334
+
335
+ .overview-content.hidden {
336
+ display: none;
337
+ }
338
+
339
+ /* Custom Theme CSS */
340
+ {{ customThemeCSS }}
341
+
342
+ /* Custom CSS */
343
+ {{ customCss }}
344
+ </style>
345
+ </head>
346
+ <body class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-white" style="{{ cssVarsString }}">
347
+ {% include "header.njk" %}
348
+ {% include "sidebar.njk" %}
349
+
350
+ <div class="flex flex-col min-h-screen">
351
+ <!-- Main content with left margin to account for fixed sidebar -->
352
+ <main class="flex-1 overflow-y-auto pb-16 ml-80">
353
+ {% block content %}{% endblock %}
354
+ </main>
355
+
356
+ <!-- Footer is already fixed in footer.njk -->
357
+ </div>
358
+ {% include "footer.njk" %}
359
+
360
+ <script>
361
+ // Initialize Lucide icons
362
+ lucide.createIcons();
363
+
364
+ // Global state
365
+ let currentEndpoint = null;
366
+
367
+ // Copy to clipboard function
368
+ async function copyToClipboard(text) {
369
+ try {
370
+ await navigator.clipboard.writeText(text);
371
+ // Show success feedback
372
+ const event = new CustomEvent('clipboard-success');
373
+ document.dispatchEvent(event);
374
+ } catch (err) {
375
+ console.error('Failed to copy text: ', err);
376
+ // Fallback for older browsers
377
+ const textArea = document.createElement('textarea');
378
+ textArea.value = text;
379
+ document.body.appendChild(textArea);
380
+ textArea.select();
381
+ document.execCommand('copy');
382
+ document.body.removeChild(textArea);
383
+ }
384
+ }
385
+
386
+ // Setup event listeners when DOM is loaded
387
+ document.addEventListener('DOMContentLoaded', function() {
388
+ // Setup search functionality
389
+ const searchInput = document.getElementById('search-input');
390
+ if (searchInput) {
391
+ searchInput.addEventListener('input', (e) => {
392
+ const searchTerm = e.target.value.toLowerCase();
393
+ const endpoints = document.querySelectorAll('.endpoint-item');
394
+ const tagSections = document.querySelectorAll('.tag-section');
395
+
396
+ endpoints.forEach(endpoint => {
397
+ const text = endpoint.textContent.toLowerCase();
398
+ const parent = endpoint.closest('.tag-section');
399
+
400
+ if (text.includes(searchTerm)) {
401
+ endpoint.style.display = '';
402
+ if (parent) parent.style.display = '';
403
+ } else {
404
+ endpoint.style.display = 'none';
405
+ }
406
+ });
407
+
408
+ // Hide empty tag sections
409
+ tagSections.forEach(section => {
410
+ const visibleEndpoints = section.querySelectorAll('.endpoint-item:not([style*="display: none"])');
411
+ section.style.display = visibleEndpoints.length > 0 ? '' : 'none';
412
+ });
413
+
414
+ lucide.createIcons();
415
+ });
416
+ }
417
+
418
+ // Theme toggle functionality
419
+ const themeToggle = document.getElementById('theme-toggle');
420
+ if (themeToggle) {
421
+ themeToggle.addEventListener('click', () => {
422
+ document.documentElement.classList.toggle('dark');
423
+ lucide.createIcons();
424
+ localStorage.setItem('flexdoc-theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light');
425
+ });
426
+ }
427
+
428
+ // Check for saved theme preference
429
+ const savedTheme = localStorage.getItem('flexdoc-theme');
430
+ if (savedTheme === 'dark' && !document.documentElement.classList.contains('dark')) {
431
+ document.documentElement.classList.add('dark');
432
+ }
433
+
434
+ // Setup endpoint navigation
435
+ setupEndpointNavigation();
436
+
437
+ // Setup collapsible sections
438
+ setupCollapsibleSections();
439
+
440
+ // Setup code tabs
441
+ setupCodeTabs();
442
+
443
+ // Setup copy buttons
444
+ setupCopyButtons();
445
+
446
+ // Setup tag toggles
447
+ setupTagToggles();
448
+
449
+ // Setup download spec button functionality
450
+ setupDownloadSpecButton();
451
+ });
452
+
453
+ // Endpoint navigation functionality
454
+ function setupEndpointNavigation() {
455
+ const endpointLinks = document.querySelectorAll('.endpoint-link');
456
+ const overviewContent = document.querySelector('.overview-content');
457
+ const endpointDetails = document.querySelectorAll('.endpoint-detail');
458
+
459
+ endpointLinks.forEach(link => {
460
+ link.addEventListener('click', (e) => {
461
+ e.preventDefault();
462
+
463
+ const targetId = link.getAttribute('data-endpoint');
464
+
465
+ // Remove active class from all links
466
+ endpointLinks.forEach(l => l.classList.remove('active'));
467
+
468
+ // Add active class to clicked link
469
+ link.classList.add('active');
470
+
471
+ // Hide overview
472
+ if (overviewContent) {
473
+ overviewContent.classList.add('hidden');
474
+ }
475
+
476
+ // Hide all endpoint details
477
+ endpointDetails.forEach(detail => {
478
+ detail.classList.remove('active');
479
+ });
480
+
481
+ // Show target endpoint detail
482
+ const targetDetail = document.getElementById(targetId);
483
+ if (targetDetail) {
484
+ targetDetail.classList.add('active');
485
+ }
486
+
487
+ currentEndpoint = targetId;
488
+ });
489
+ });
490
+
491
+ // Handle hash navigation
492
+ if (window.location.hash) {
493
+ const targetId = window.location.hash.substring(1);
494
+ const targetLink = document.querySelector(`[data-endpoint="${targetId}"]`);
495
+ if (targetLink) {
496
+ targetLink.click();
497
+ }
498
+ }
499
+ }
500
+
501
+ // Tag toggle functionality
502
+ function setupTagToggles() {
503
+ const tagToggles = document.querySelectorAll('.tag-toggle');
504
+
505
+ tagToggles.forEach(toggle => {
506
+ toggle.addEventListener('click', () => {
507
+ const targetId = toggle.getAttribute('data-target');
508
+ const target = document.getElementById(targetId);
509
+ const icon = toggle.querySelector('[data-lucide]');
510
+
511
+ if (target) {
512
+ target.classList.toggle('expanded');
513
+
514
+ // Update icon
515
+ if (target.classList.contains('expanded')) {
516
+ icon.setAttribute('data-lucide', 'chevron-down');
517
+ } else {
518
+ icon.setAttribute('data-lucide', 'chevron-right');
519
+ }
520
+
521
+ lucide.createIcons();
522
+ }
523
+ });
524
+ });
525
+ }
526
+
527
+ // Collapsible sections functionality
528
+ function setupCollapsibleSections() {
529
+ const toggleButtons = document.querySelectorAll('[data-toggle]');
530
+ toggleButtons.forEach(button => {
531
+ button.addEventListener('click', () => {
532
+ const targetId = button.getAttribute('data-toggle');
533
+ const target = document.getElementById(targetId);
534
+ const icon = button.querySelector('[data-lucide]');
535
+
536
+ if (target) {
537
+ target.classList.toggle('expanded');
538
+
539
+ // Update icon
540
+ if (target.classList.contains('expanded')) {
541
+ icon.setAttribute('data-lucide', 'chevron-down');
542
+ } else {
543
+ icon.setAttribute('data-lucide', 'chevron-right');
544
+ }
545
+
546
+ lucide.createIcons();
547
+ }
548
+ });
549
+ });
550
+ }
551
+
552
+ // Code tabs functionality
553
+ function setupCodeTabs() {
554
+ const codeTabs = document.querySelectorAll('.code-tab');
555
+ codeTabs.forEach(tab => {
556
+ tab.addEventListener('click', () => {
557
+ const targetId = tab.getAttribute('data-target');
558
+ if (!targetId) return;
559
+
560
+ const tabGroup = tab.closest('.code-tabs');
561
+ if (!tabGroup) return;
562
+
563
+ // Deactivate all tabs in this group
564
+ const tabs = tabGroup.querySelectorAll('.code-tab');
565
+ tabs.forEach(t => {
566
+ t.classList.remove('border-primary-500', 'text-primary-600');
567
+ t.classList.add('border-transparent', 'text-gray-500');
568
+ });
569
+
570
+ // Activate clicked tab
571
+ tab.classList.remove('border-transparent', 'text-gray-500');
572
+ tab.classList.add('border-primary-500', 'text-primary-600');
573
+
574
+ // Hide all panels in this group
575
+ const panels = tabGroup.querySelectorAll('.code-panel');
576
+ panels.forEach(panel => panel.classList.add('hidden'));
577
+
578
+ // Show target panel
579
+ const targetPanel = document.getElementById(targetId);
580
+ if (targetPanel) {
581
+ targetPanel.classList.remove('hidden');
582
+ }
583
+ });
584
+ });
585
+ }
586
+
587
+ // Copy buttons functionality
588
+ function setupCopyButtons() {
589
+ const copyButtons = document.querySelectorAll('[data-copy]');
590
+ copyButtons.forEach(button => {
591
+ button.addEventListener('click', async () => {
592
+ const text = button.getAttribute('data-copy');
593
+ await copyToClipboard(text);
594
+
595
+ // Visual feedback
596
+ const originalText = button.textContent;
597
+ const icon = button.querySelector('[data-lucide]');
598
+
599
+ button.textContent = 'Copied!';
600
+ if (icon) {
601
+ icon.setAttribute('data-lucide', 'check');
602
+ lucide.createIcons();
603
+ }
604
+
605
+ setTimeout(() => {
606
+ button.textContent = originalText;
607
+ if (icon) {
608
+ icon.setAttribute('data-lucide', 'copy');
609
+ lucide.createIcons();
610
+ }
611
+ }, 2000);
612
+ });
613
+ });
614
+ }
615
+
616
+ // Setup download spec button functionality
617
+ function setupDownloadSpecButton() {
618
+ const downloadBtn = document.getElementById('download-spec-btn');
619
+ if (downloadBtn) {
620
+ downloadBtn.addEventListener('click', () => {
621
+ // Create a blob with the spec JSON
622
+ const specJson = `{{ specJsonString | safe }}`;
623
+ const blob = new Blob([specJson], { type: 'application/json' });
624
+
625
+ // Create a download link
626
+ const url = URL.createObjectURL(blob);
627
+ const a = document.createElement('a');
628
+ a.href = url;
629
+ a.download = '{{ title | default("openapi-spec") }}.json';
630
+
631
+ // Trigger the download
632
+ document.body.appendChild(a);
633
+ a.click();
634
+
635
+ // Clean up
636
+ setTimeout(() => {
637
+ document.body.removeChild(a);
638
+ URL.revokeObjectURL(url);
639
+ }, 0);
640
+ });
641
+ }
642
+ }
643
+
644
+ {{ customJs }}
645
+ </script>
646
+ </body>
647
+ </html>