@eventcatalog/core 2.63.0 → 2.64.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 (47) hide show
  1. package/dist/analytics/analytics.cjs +1 -1
  2. package/dist/analytics/analytics.js +2 -2
  3. package/dist/analytics/log-build.cjs +1 -1
  4. package/dist/analytics/log-build.js +3 -3
  5. package/dist/{chunk-I2FMV7LN.js → chunk-6AMZOBWI.js} +1 -1
  6. package/dist/{chunk-IRFM5IS7.js → chunk-CWGFHLMX.js} +1 -1
  7. package/dist/{chunk-GA274FBN.js → chunk-PLMTJHGH.js} +1 -1
  8. package/dist/constants.cjs +1 -1
  9. package/dist/constants.js +1 -1
  10. package/dist/eventcatalog.cjs +1 -1
  11. package/dist/eventcatalog.js +3 -3
  12. package/eventcatalog/astro.config.mjs +2 -1
  13. package/eventcatalog/public/icons/avro.svg +21 -0
  14. package/eventcatalog/public/icons/json-schema.svg +6 -0
  15. package/eventcatalog/public/icons/proto.svg +10 -0
  16. package/eventcatalog/src/components/Grids/utils.tsx +5 -3
  17. package/eventcatalog/src/components/MDX/RemoteFile.astro +5 -11
  18. package/eventcatalog/src/components/MDX/SchemaViewer/SchemaViewerRoot.astro +41 -6
  19. package/eventcatalog/src/components/SchemaExplorer/ApiAccessSection.tsx +139 -0
  20. package/eventcatalog/src/components/SchemaExplorer/AvroSchemaViewer.tsx +423 -0
  21. package/eventcatalog/src/components/SchemaExplorer/DiffViewer.tsx +102 -0
  22. package/eventcatalog/src/components/SchemaExplorer/JSONSchemaViewer.tsx +740 -0
  23. package/eventcatalog/src/components/SchemaExplorer/OwnersSection.tsx +56 -0
  24. package/eventcatalog/src/components/SchemaExplorer/Pagination.tsx +33 -0
  25. package/eventcatalog/src/components/SchemaExplorer/ProducersConsumersSection.tsx +91 -0
  26. package/eventcatalog/src/components/SchemaExplorer/SchemaCodeModal.tsx +93 -0
  27. package/eventcatalog/src/components/SchemaExplorer/SchemaContentViewer.tsx +130 -0
  28. package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsHeader.tsx +181 -0
  29. package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +232 -0
  30. package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +415 -0
  31. package/eventcatalog/src/components/SchemaExplorer/SchemaFilters.tsx +174 -0
  32. package/eventcatalog/src/components/SchemaExplorer/SchemaListItem.tsx +73 -0
  33. package/eventcatalog/src/components/SchemaExplorer/SchemaViewerModal.tsx +77 -0
  34. package/eventcatalog/src/components/SchemaExplorer/VersionHistoryModal.tsx +72 -0
  35. package/eventcatalog/src/components/SchemaExplorer/types.ts +45 -0
  36. package/eventcatalog/src/components/SchemaExplorer/utils.ts +81 -0
  37. package/eventcatalog/src/components/SideNav/ListViewSideBar/index.tsx +33 -2
  38. package/eventcatalog/src/components/Tables/columns/MessageTableColumns.tsx +2 -2
  39. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +10 -0
  40. package/eventcatalog/src/pages/api/schemas/[collection]/[id]/[version]/index.ts +45 -0
  41. package/eventcatalog/src/pages/api/schemas/services/[id]/[version]/[specification]/index.ts +51 -0
  42. package/eventcatalog/src/pages/docs/llm/schemas.txt.ts +86 -0
  43. package/eventcatalog/src/pages/schemas/index.astro +175 -0
  44. package/eventcatalog/src/utils/files.ts +9 -0
  45. package/package.json +1 -1
  46. package/eventcatalog/src/components/MDX/SchemaViewer/SchemaProperty.astro +0 -204
  47. package/eventcatalog/src/components/MDX/SchemaViewer/SchemaViewer.astro +0 -705
@@ -1,705 +0,0 @@
1
- ---
2
- // src/components/SchemaViewer.astro
3
- import SchemaProperty from './SchemaProperty.astro';
4
-
5
- interface Props {
6
- schema: Record<string, any>;
7
- schemaPath: string;
8
- title: string;
9
- maxHeight: string;
10
- file: string;
11
- id: string;
12
- expand?: boolean | string;
13
- search?: boolean | string;
14
- }
15
-
16
- const { id, file, title, maxHeight, schema, expand = false, search = true } = Astro.props;
17
-
18
- // Convert string props to booleans (MDX passes strings)
19
- const expandBool = expand === true || expand === 'true';
20
- const searchBool = search !== false && search !== 'false';
21
-
22
- // This will be used to pass expand state via data attribute
23
- const expandData = expandBool ? 'true' : 'false';
24
- const searchData = searchBool ? 'true' : 'false';
25
-
26
- // Count total properties recursively
27
- function countProperties(obj: any): number {
28
- if (!obj || typeof obj !== 'object') return 0;
29
-
30
- let count = 0;
31
- if (obj.properties) {
32
- count += Object.keys(obj.properties).length;
33
- Object.values(obj.properties).forEach((prop: any) => {
34
- count += countProperties(prop);
35
- });
36
- }
37
- if (obj.items) {
38
- count += countProperties(obj.items);
39
- }
40
- // Handle root array items that have been processed
41
- if (obj._isRootArrayItem && obj._rootArraySchema?.items) {
42
- // Don't double count, we're already counting the properties above
43
- }
44
- return count;
45
- }
46
-
47
- // Function to merge allOf schemas
48
- function mergeAllOfSchemas(schemaWithProcessor: any): any {
49
- const { processSchema: processor, ...schema } = schemaWithProcessor;
50
- if (!schema.allOf) return schema;
51
-
52
- const mergedSchema: {
53
- type: string;
54
- properties: Record<string, any>;
55
- required: string[];
56
- description?: string;
57
- [key: string]: any;
58
- } = {
59
- type: schema.type || 'object',
60
- properties: {},
61
- required: [],
62
- description: schema.description,
63
- };
64
-
65
- // Copy base schema properties first (excluding allOf)
66
- Object.keys(schema).forEach((key) => {
67
- if (key !== 'allOf' && key !== 'properties' && key !== 'required' && key !== 'description') {
68
- mergedSchema[key] = schema[key];
69
- }
70
- });
71
-
72
- // Copy base properties if they exist
73
- if (schema.properties) {
74
- mergedSchema.properties = { ...schema.properties };
75
- }
76
- if (schema.required) {
77
- mergedSchema.required = [...schema.required];
78
- }
79
-
80
- schema.allOf.forEach((subSchema: any) => {
81
- // Recursively process each subSchema in case it has its own allOf, oneOf, or $ref
82
- const processedSubSchema = processor ? processor(subSchema) : subSchema;
83
-
84
- if (processedSubSchema.properties) {
85
- mergedSchema.properties = {
86
- ...mergedSchema.properties,
87
- ...processedSubSchema.properties,
88
- };
89
- }
90
- if (processedSubSchema.required) {
91
- mergedSchema.required = [...new Set([...mergedSchema.required, ...processedSubSchema.required])];
92
- }
93
- if (processedSubSchema.description && !mergedSchema.description) {
94
- mergedSchema.description = processedSubSchema.description;
95
- }
96
-
97
- // Copy other properties from subSchema
98
- Object.keys(processedSubSchema).forEach((key) => {
99
- if (key !== 'properties' && key !== 'required' && key !== 'description' && key !== 'type') {
100
- if (!mergedSchema[key]) {
101
- mergedSchema[key] = processedSubSchema[key];
102
- }
103
- }
104
- });
105
- });
106
-
107
- return mergedSchema;
108
- }
109
-
110
- function processSchema(schema: any, rootSchema?: any): any {
111
- if (!schema) return schema;
112
-
113
- // Set rootSchema for $ref resolution
114
- const root = rootSchema || schema;
115
-
116
- // Handle $ref
117
- if (schema.$ref) {
118
- const refPath = schema.$ref;
119
- let resolvedSchema = null;
120
- let defName = '';
121
-
122
- // Try draft-7 style first: #/definitions/
123
- if (refPath.startsWith('#/definitions/')) {
124
- defName = refPath.replace('#/definitions/', '');
125
- if (root.definitions && root.definitions[defName]) {
126
- resolvedSchema = root.definitions[defName];
127
- }
128
- }
129
- // Try 2020-12 style: #/$defs/
130
- else if (refPath.startsWith('#/$defs/')) {
131
- defName = refPath.replace('#/$defs/', '');
132
- if (root.$defs && root.$defs[defName]) {
133
- resolvedSchema = root.$defs[defName];
134
- }
135
- }
136
- // Try other common patterns
137
- else if (refPath.startsWith('#/components/schemas/')) {
138
- defName = refPath.replace('#/components/schemas/', '');
139
- if (root.components && root.components.schemas && root.components.schemas[defName]) {
140
- resolvedSchema = root.components.schemas[defName];
141
- }
142
- }
143
-
144
- if (resolvedSchema) {
145
- // Recursively process the referenced schema
146
- const processedSchema = processSchema(resolvedSchema, root);
147
- // Add reference info to the resolved schema
148
- return {
149
- ...processedSchema,
150
- _refPath: refPath,
151
- _refName: defName,
152
- _originalRef: schema.$ref,
153
- };
154
- }
155
-
156
- // If not found, create a placeholder schema showing the reference
157
- return {
158
- type: 'string',
159
- description: `Reference to ${refPath} (definition not found in root schema)`,
160
- title: defName || refPath.split('/').pop(),
161
- _refPath: refPath,
162
- _refName: defName,
163
- _refNotFound: true,
164
- };
165
- }
166
-
167
- if (schema.allOf) {
168
- return mergeAllOfSchemas({ ...schema, processSchema: (s: any) => processSchema(s, root) });
169
- }
170
-
171
- if (schema.oneOf) {
172
- // Process each oneOf variant and create a combined schema
173
- const processedVariants = schema.oneOf.map((variant: any) => {
174
- const processedVariant = processSchema(variant, root);
175
- return {
176
- title: processedVariant.title || variant.title || 'Unnamed Variant',
177
- required: processedVariant.required || variant.required || [],
178
- properties: processedVariant.properties || {},
179
- ...processedVariant,
180
- };
181
- });
182
-
183
- // Merge all properties from variants for display
184
- const allProperties: Record<string, any> = {};
185
- processedVariants.forEach((variant: any) => {
186
- if (variant.properties) {
187
- Object.assign(allProperties, variant.properties);
188
- }
189
- });
190
-
191
- return {
192
- ...schema,
193
- type: schema.type || 'object',
194
- properties: {
195
- ...(schema.properties || {}),
196
- ...allProperties,
197
- },
198
- variants: processedVariants,
199
- };
200
- }
201
-
202
- // Process nested schemas in properties
203
- if (schema.properties) {
204
- const processedProperties: Record<string, any> = {};
205
- Object.entries(schema.properties).forEach(([key, prop]: [string, any]) => {
206
- processedProperties[key] = processSchema(prop, root);
207
- });
208
- schema = { ...schema, properties: processedProperties };
209
- }
210
-
211
- // Process array items
212
- if (schema.type === 'array' && schema.items) {
213
- schema = { ...schema, items: processSchema(schema.items, root) };
214
- }
215
-
216
- return schema;
217
- }
218
-
219
- const processedSchema = processSchema(schema);
220
-
221
- // Handle root-level array schemas
222
- let displaySchema = processedSchema;
223
- let isRootArray = false;
224
-
225
- if (processedSchema.type === 'array' && processedSchema.items) {
226
- isRootArray = true;
227
- // For root arrays, we want to display the items' properties
228
- if (processedSchema.items.type === 'object' && processedSchema.items.properties) {
229
- displaySchema = {
230
- ...processedSchema.items,
231
- description: processedSchema.description || processedSchema.items.description,
232
- _isRootArrayItem: true,
233
- _rootArraySchema: processedSchema,
234
- };
235
- } else if (processedSchema.items.allOf || processedSchema.items.oneOf || processedSchema.items.$ref) {
236
- // Process complex array item schemas
237
- displaySchema = {
238
- ...processSchema(processedSchema.items),
239
- description: processedSchema.description || processedSchema.items.description,
240
- _isRootArrayItem: true,
241
- _rootArraySchema: processedSchema,
242
- };
243
- }
244
- }
245
-
246
- const { description, properties, required = [], variants } = displaySchema;
247
-
248
- // Count total properties after processing
249
- const totalProperties = countProperties(displaySchema);
250
-
251
- // Generate a unique ID for this instance
252
- const instanceId = `${id}-${Math.random().toString(36).substring(2, 9)}`;
253
- ---
254
-
255
- <div id={id} class="not-prose my-4" data-expand={expandData} data-search={searchData}>
256
- {title && <h2 class="text-3xl font-bold mb-2 !mt-0">{title}</h2>}
257
- <div
258
- class="schema-viewer p-4 pt-0 bg-white overflow-y-auto rounded-lg border border-gray-100 shadow-sm font-sans"
259
- style={{
260
- maxHeight: maxHeight ? `${maxHeight}px` : `30em`,
261
- minHeight: '15em',
262
- }}
263
- >
264
- {
265
- searchBool && (
266
- <div
267
- class="schema-toolbar sticky top-0 z-10 bg-white pt-4 px-4 -mx-4 mb-4 pb-3 border-b border-gray-100 shadow-sm"
268
- id={`${instanceId}-toolbar`}
269
- >
270
- <div class="flex flex-col sm:flex-row gap-3">
271
- <div class="flex-1 relative">
272
- <input
273
- type="text"
274
- id={`${instanceId}-search`}
275
- placeholder="Search properties..."
276
- class="w-full px-3 py-1.5 pr-20 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
277
- />
278
- <div
279
- class="absolute right-2 top-1/2 transform -translate-y-1/2 flex items-center gap-1"
280
- id={`${instanceId}-nav-buttons`}
281
- >
282
- <button
283
- id={`${instanceId}-prev-match`}
284
- class="p-1 text-gray-400 hover:text-gray-600 disabled:opacity-30 disabled:cursor-not-allowed"
285
- disabled
286
- title="Previous match"
287
- >
288
- <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
289
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
290
- </svg>
291
- </button>
292
- <button
293
- id={`${instanceId}-next-match`}
294
- class="p-1 text-gray-400 hover:text-gray-600 disabled:opacity-30 disabled:cursor-not-allowed"
295
- disabled
296
- title="Next match"
297
- >
298
- <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
299
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
300
- </svg>
301
- </button>
302
- </div>
303
- </div>
304
- <div class="flex items-center gap-2">
305
- <button
306
- id={`${instanceId}-expand-all`}
307
- class="px-3 py-1.5 text-xs font-medium text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-500"
308
- >
309
- Expand All
310
- </button>
311
- <button
312
- id={`${instanceId}-collapse-all`}
313
- class="px-3 py-1.5 text-xs font-medium text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-500"
314
- >
315
- Collapse All
316
- </button>
317
- <div class="text-xs text-gray-500">
318
- {totalProperties} {totalProperties === 1 ? 'property' : 'properties'}
319
- </div>
320
- </div>
321
- </div>
322
- <div id={`${instanceId}-search-results`} class="mt-2 text-xs text-gray-600 hidden" />
323
- </div>
324
- )
325
- }
326
- {
327
- isRootArray && (
328
- <div class="mb-4 p-3 bg-blue-50 border border-blue-200 rounded-md">
329
- <div class="flex items-center space-x-2">
330
- <span class="text-blue-600 font-medium text-sm">Array Schema</span>
331
- <span class="text-blue-500 font-mono text-xs">array[object]</span>
332
- </div>
333
- <p class="text-blue-700 text-xs mt-1">
334
- This schema defines an array of objects. Each item in the array has the properties shown below.
335
- </p>
336
- </div>
337
- )
338
- }
339
- {description && <p class="text-gray-600 text-xs mb-5">{description}</p>}
340
-
341
- {
342
- variants && (
343
- <div class="mb-4">
344
- <div class="flex items-center space-x-2">
345
- <span class="text-sm text-gray-600">(one of)</span>
346
- <select
347
- id={`${instanceId}-variant-selector`}
348
- class="form-select text-sm border-gray-300 rounded-md shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
349
- >
350
- {variants.map((variant: any, index: number) => (
351
- <option value={index}>{variant.title}</option>
352
- ))}
353
- </select>
354
- </div>
355
- </div>
356
- )
357
- }
358
-
359
- {
360
- properties && (
361
- <div>
362
- <div id={`${instanceId}-properties`}>
363
- {Object.entries(properties).map(([name, details]) => (
364
- <SchemaProperty
365
- name={name}
366
- details={details}
367
- isRequired={variants ? false : required.includes(name)}
368
- level={0}
369
- expand={expandBool}
370
- />
371
- ))}
372
- </div>
373
- <div id={`${instanceId}-no-results`} class="hidden text-center py-8">
374
- <div class="text-gray-400 text-sm">
375
- <svg class="mx-auto h-12 w-12 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
376
- <path
377
- stroke-linecap="round"
378
- stroke-linejoin="round"
379
- stroke-width="2"
380
- d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
381
- />
382
- </svg>
383
- <p>No properties match your search</p>
384
- <p class="text-xs mt-1">Try a different search term or clear the search to see all properties</p>
385
- </div>
386
- </div>
387
- </div>
388
- )
389
- }
390
-
391
- {!properties && !isRootArray && <p class="text-gray-500 text-sm">Schema does not contain any properties.</p>}
392
- {
393
- !properties && isRootArray && (
394
- <div class="text-center py-8">
395
- <div class="text-gray-500 text-sm">
396
- <p>
397
- This array contains items of type:{' '}
398
- <span class="font-mono text-blue-600">{processedSchema.items?.type || 'unknown'}</span>
399
- </p>
400
- {processedSchema.items?.description && <p class="text-xs mt-2 text-gray-600">{processedSchema.items.description}</p>}
401
- </div>
402
- </div>
403
- )
404
- }
405
- </div>
406
- </div>
407
-
408
- <script define:vars={{ instanceId, variants, id, file }}>
409
- // Check if expand is set via data attribute (from portal movement)
410
- function checkAndExpandAll() {
411
- const schemaViewerClient = document.getElementById(`${id}-${file}-SchemaViewer-client`);
412
- if (schemaViewerClient && schemaViewerClient.getAttribute('data-expand') === 'true') {
413
- // Find all toggle buttons within this schema viewer
414
- const toggleButtons = schemaViewerClient.querySelectorAll('.property-toggle');
415
- toggleButtons.forEach((button) => {
416
- if (button.getAttribute('aria-expanded') === 'false') {
417
- button.click(); // Trigger the existing toggle logic
418
- }
419
- });
420
- }
421
- }
422
-
423
- // Setup search and utilities
424
- function setupSchemaUtilities() {
425
- const schemaViewerClient = document.getElementById(id);
426
- if (!schemaViewerClient || schemaViewerClient.getAttribute('data-search') !== 'true') return;
427
-
428
- const searchInput = document.getElementById(`${instanceId}-search`);
429
- const expandAllBtn = document.getElementById(`${instanceId}-expand-all`);
430
- const collapseAllBtn = document.getElementById(`${instanceId}-collapse-all`);
431
- const searchResults = document.getElementById(`${instanceId}-search-results`);
432
- const propertiesContainer = document.getElementById(`${instanceId}-properties`);
433
- const noResultsMessage = document.getElementById(`${instanceId}-no-results`);
434
- const prevMatchBtn = document.getElementById(`${instanceId}-prev-match`);
435
- const nextMatchBtn = document.getElementById(`${instanceId}-next-match`);
436
-
437
- let currentMatches = [];
438
- let currentMatchIndex = -1;
439
-
440
- if (!searchInput || !propertiesContainer) return;
441
-
442
- // Expand All functionality
443
- if (expandAllBtn) {
444
- expandAllBtn.addEventListener('click', () => {
445
- const toggleButtons = schemaViewerClient.querySelectorAll('.property-toggle');
446
- toggleButtons.forEach((button) => {
447
- if (button.getAttribute('aria-expanded') === 'false') {
448
- button.click();
449
- }
450
- });
451
- });
452
- }
453
-
454
- // Collapse All functionality
455
- if (collapseAllBtn) {
456
- collapseAllBtn.addEventListener('click', () => {
457
- const toggleButtons = schemaViewerClient.querySelectorAll('.property-toggle');
458
- toggleButtons.forEach((button) => {
459
- if (button.getAttribute('aria-expanded') === 'true') {
460
- button.click();
461
- }
462
- });
463
- });
464
- }
465
-
466
- // Helper functions
467
- function scrollToMatch(container) {
468
- container.scrollIntoView({
469
- behavior: 'smooth',
470
- block: 'center',
471
- inline: 'nearest',
472
- });
473
- }
474
-
475
- function updateMatchDisplay() {
476
- // Remove previous current match highlighting
477
- currentMatches.forEach((match, index) => {
478
- match.classList.remove('search-current-match');
479
- if (index === currentMatchIndex) {
480
- match.classList.add('search-current-match');
481
- }
482
- });
483
-
484
- // Update navigation buttons
485
- if (prevMatchBtn && nextMatchBtn) {
486
- prevMatchBtn.disabled = currentMatches.length === 0 || currentMatchIndex <= 0;
487
- nextMatchBtn.disabled = currentMatches.length === 0 || currentMatchIndex >= currentMatches.length - 1;
488
- }
489
-
490
- // Update search results with current position
491
- if (searchResults && currentMatches.length > 0) {
492
- searchResults.textContent = `${currentMatchIndex + 1} of ${currentMatches.length} ${currentMatches.length === 1 ? 'match' : 'matches'}`;
493
- }
494
- }
495
-
496
- function performSearch(searchTerm) {
497
- const propertyContainers = propertiesContainer.querySelectorAll('.property-container');
498
- currentMatches = [];
499
- currentMatchIndex = -1;
500
-
501
- if (searchTerm === '') {
502
- // Reset search
503
- propertyContainers.forEach((container) => {
504
- container.classList.remove('search-match', 'search-no-match', 'search-current-match', 'search-dimmed');
505
- const nameEl = container.querySelector('.font-semibold');
506
- if (nameEl) {
507
- nameEl.innerHTML = nameEl.textContent;
508
- }
509
- });
510
- searchResults.classList.add('hidden');
511
- if (noResultsMessage) {
512
- noResultsMessage.classList.add('hidden');
513
- }
514
- updateMatchDisplay();
515
-
516
- // Scroll back to top when search is cleared
517
- // Use the same container that we're already working with
518
- const scrollableContainer = propertiesContainer.closest('.schema-viewer');
519
- if (scrollableContainer) {
520
- scrollableContainer.scrollTo({
521
- top: 0,
522
- behavior: 'smooth',
523
- });
524
- }
525
- return;
526
- }
527
-
528
- // Find all matches
529
- propertyContainers.forEach((container) => {
530
- const nameEl = container.querySelector('.font-semibold');
531
-
532
- if (!nameEl) return;
533
-
534
- const propName = nameEl.textContent.toLowerCase();
535
-
536
- if (propName.includes(searchTerm)) {
537
- container.classList.add('search-match');
538
- container.classList.remove('search-dimmed');
539
- currentMatches.push(container);
540
-
541
- // Highlight the search term in the property name
542
- const regex = new RegExp(`(${searchTerm})`, 'gi');
543
- nameEl.innerHTML = nameEl.textContent.replace(regex, '<mark class="bg-yellow-200 rounded px-0.5">$1</mark>');
544
-
545
- // Expand parent containers to show the match
546
- let parent = container.parentElement;
547
- while (parent && parent.id !== `${instanceId}-properties`) {
548
- if (parent.classList.contains('nested-content') && parent.classList.contains('hidden')) {
549
- const parentPropertyContainer = parent.closest('.property-container');
550
- if (parentPropertyContainer) {
551
- const toggleBtn = parentPropertyContainer.querySelector('.property-toggle');
552
- if (toggleBtn && toggleBtn.getAttribute('aria-expanded') === 'false') {
553
- toggleBtn.click();
554
- }
555
- }
556
- }
557
- parent = parent.parentElement;
558
- }
559
- } else {
560
- container.classList.remove('search-match', 'search-current-match');
561
- container.classList.add('search-dimmed');
562
- nameEl.innerHTML = nameEl.textContent;
563
- }
564
- });
565
-
566
- // Show results
567
- if (searchResults) {
568
- searchResults.classList.remove('hidden');
569
- if (currentMatches.length === 0) {
570
- searchResults.textContent = 'No properties found';
571
- searchResults.classList.add('text-red-600');
572
- searchResults.classList.remove('text-gray-600');
573
- if (noResultsMessage) {
574
- noResultsMessage.classList.remove('hidden');
575
- }
576
- } else {
577
- searchResults.classList.remove('text-red-600');
578
- searchResults.classList.add('text-gray-600');
579
- if (noResultsMessage) {
580
- noResultsMessage.classList.add('hidden');
581
- }
582
- // Go to first match
583
- currentMatchIndex = 0;
584
- scrollToMatch(currentMatches[0]);
585
- }
586
- }
587
-
588
- updateMatchDisplay();
589
- }
590
-
591
- // Search functionality
592
- let searchTimeout;
593
- searchInput.addEventListener('input', (e) => {
594
- clearTimeout(searchTimeout);
595
- searchTimeout = setTimeout(() => {
596
- const searchTerm = e.target.value.toLowerCase().trim();
597
- performSearch(searchTerm);
598
- }, 300);
599
- });
600
-
601
- // Navigation functionality
602
- if (prevMatchBtn) {
603
- prevMatchBtn.addEventListener('click', () => {
604
- if (currentMatchIndex > 0) {
605
- currentMatchIndex--;
606
- scrollToMatch(currentMatches[currentMatchIndex]);
607
- updateMatchDisplay();
608
- }
609
- });
610
- }
611
-
612
- if (nextMatchBtn) {
613
- nextMatchBtn.addEventListener('click', () => {
614
- if (currentMatchIndex < currentMatches.length - 1) {
615
- currentMatchIndex++;
616
- scrollToMatch(currentMatches[currentMatchIndex]);
617
- updateMatchDisplay();
618
- }
619
- });
620
- }
621
-
622
- // Keyboard navigation
623
- searchInput.addEventListener('keydown', (e) => {
624
- if (e.key === 'Enter') {
625
- e.preventDefault();
626
- if (e.shiftKey && prevMatchBtn && !prevMatchBtn.disabled) {
627
- prevMatchBtn.click();
628
- } else if (!e.shiftKey && nextMatchBtn && !nextMatchBtn.disabled) {
629
- nextMatchBtn.click();
630
- }
631
- }
632
- });
633
- }
634
-
635
- if (variants) {
636
- const selector = document.getElementById(`${instanceId}-variant-selector`);
637
- const propertiesContainer = document.getElementById(`${instanceId}-properties`);
638
-
639
- function updateRequiredFields() {
640
- const selectedVariant = variants[selector.value];
641
- const properties = propertiesContainer.querySelectorAll('.property-container');
642
-
643
- properties.forEach((prop) => {
644
- const nameEl = prop.querySelector('.font-semibold');
645
- if (!nameEl) return;
646
-
647
- const name = nameEl.textContent;
648
- const requiredBadge = prop.querySelector('.text-red-600');
649
-
650
- if (selectedVariant.required.includes(name)) {
651
- if (!requiredBadge) {
652
- const badge = document.createElement('span');
653
- badge.className = 'text-red-600 text-xs ml-3 flex-shrink-0';
654
- badge.textContent = 'required';
655
- nameEl.parentElement.appendChild(badge);
656
- }
657
- } else {
658
- requiredBadge?.remove();
659
- }
660
- });
661
- }
662
-
663
- selector.addEventListener('change', updateRequiredFields);
664
- // Initialize with first variant
665
- updateRequiredFields();
666
- }
667
-
668
- // Check expand state after page loads and after portal movement
669
- document.addEventListener('astro:page-load', () => {
670
- // Small delay to ensure portal movement has completed
671
- setTimeout(() => {
672
- checkAndExpandAll();
673
- setupSchemaUtilities();
674
- }, 100);
675
- });
676
-
677
- // Also check immediately in case the component is already in place
678
- checkAndExpandAll();
679
- setupSchemaUtilities();
680
- </script>
681
-
682
- <style>
683
- .schema-viewer code {
684
- font-family: 'Courier New', Courier, monospace;
685
- }
686
-
687
- /* Search highlighting styles */
688
- .search-dimmed {
689
- opacity: 0.4;
690
- transition: opacity 0.2s ease;
691
- }
692
-
693
- .search-match {
694
- opacity: 1;
695
- transition: opacity 0.2s ease;
696
- }
697
-
698
- .search-current-match {
699
- background-color: rgba(59, 130, 246, 0.1);
700
- border-left: 3px solid #3b82f6;
701
- padding-left: 8px;
702
- margin-left: -11px;
703
- transition: all 0.2s ease;
704
- }
705
- </style>