@emeryld/rrroutes-contract 2.7.3 → 2.7.5
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.
- package/package.json +1 -1
- package/tools/finalized-leaves-viewer.html +434 -135
package/package.json
CHANGED
|
@@ -6,14 +6,15 @@
|
|
|
6
6
|
<title>Finalized Leaves Viewer</title>
|
|
7
7
|
<style>
|
|
8
8
|
:root {
|
|
9
|
-
--bg: #
|
|
9
|
+
--bg: #f6f8fc;
|
|
10
10
|
--surface: #ffffff;
|
|
11
|
-
--surface-2: #
|
|
12
|
-
--border: #
|
|
13
|
-
--text: #
|
|
14
|
-
--muted: #
|
|
11
|
+
--surface-2: #f1f4f9;
|
|
12
|
+
--border: #d9e0eb;
|
|
13
|
+
--text: #182033;
|
|
14
|
+
--muted: #64708b;
|
|
15
15
|
--accent: #1858c6;
|
|
16
16
|
--ok: #1f8f4e;
|
|
17
|
+
--danger: #d12b2b;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
* {
|
|
@@ -24,8 +25,7 @@
|
|
|
24
25
|
margin: 0;
|
|
25
26
|
font-family: 'Iosevka Web', 'SFMono-Regular', Menlo, Consolas, monospace;
|
|
26
27
|
color: var(--text);
|
|
27
|
-
background:
|
|
28
|
-
linear-gradient(180deg, var(--bg), #eef2fa);
|
|
28
|
+
background: var(--bg);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
mark {
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
|
|
44
44
|
.card {
|
|
45
45
|
background: var(--surface);
|
|
46
|
-
border: 1px solid
|
|
47
|
-
border-radius:
|
|
48
|
-
padding:
|
|
46
|
+
border: 1px solid #e7ecf4;
|
|
47
|
+
border-radius: 6px;
|
|
48
|
+
padding: 12px;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
.controls {
|
|
@@ -59,22 +59,41 @@
|
|
|
59
59
|
gap: 8px;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
.field-actions {
|
|
63
|
+
display: flex;
|
|
64
|
+
flex-wrap: wrap;
|
|
65
|
+
gap: 8px;
|
|
66
|
+
}
|
|
67
|
+
|
|
62
68
|
.field-item {
|
|
63
69
|
display: inline-flex;
|
|
64
70
|
align-items: center;
|
|
65
71
|
gap: 6px;
|
|
66
|
-
padding:
|
|
67
|
-
border: 1px solid var(--border);
|
|
68
|
-
border-radius: 8px;
|
|
69
|
-
background: var(--surface-2);
|
|
72
|
+
padding: 3px 0;
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
input[type='text']
|
|
75
|
+
input[type='text'],
|
|
76
|
+
select {
|
|
73
77
|
width: 100%;
|
|
74
78
|
border: 1px solid var(--border);
|
|
75
|
-
border-radius:
|
|
76
|
-
padding: 10px
|
|
79
|
+
border-radius: 4px;
|
|
80
|
+
padding: 8px 10px;
|
|
81
|
+
font: inherit;
|
|
82
|
+
background: #fff;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
button {
|
|
86
|
+
border: 1px solid var(--border);
|
|
87
|
+
border-radius: 4px;
|
|
88
|
+
padding: 7px 10px;
|
|
77
89
|
font: inherit;
|
|
90
|
+
background: #fff;
|
|
91
|
+
color: var(--text);
|
|
92
|
+
cursor: pointer;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
button:hover {
|
|
96
|
+
background: var(--surface-2);
|
|
78
97
|
}
|
|
79
98
|
|
|
80
99
|
.meta {
|
|
@@ -85,14 +104,12 @@
|
|
|
85
104
|
#results {
|
|
86
105
|
margin-top: 14px;
|
|
87
106
|
display: grid;
|
|
88
|
-
gap:
|
|
107
|
+
gap: 0;
|
|
89
108
|
}
|
|
90
109
|
|
|
91
110
|
details.leaf {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
border-radius: 10px;
|
|
95
|
-
padding: 8px 10px;
|
|
111
|
+
border-top: 1px solid var(--border);
|
|
112
|
+
padding: 10px 2px;
|
|
96
113
|
}
|
|
97
114
|
|
|
98
115
|
summary {
|
|
@@ -103,15 +120,13 @@
|
|
|
103
120
|
|
|
104
121
|
.leaf-content {
|
|
105
122
|
display: grid;
|
|
106
|
-
gap:
|
|
123
|
+
gap: 4px;
|
|
107
124
|
margin-top: 10px;
|
|
108
125
|
}
|
|
109
126
|
|
|
110
127
|
.section {
|
|
111
|
-
border: 1px solid
|
|
112
|
-
|
|
113
|
-
padding: 10px;
|
|
114
|
-
background: #fbfcff;
|
|
128
|
+
border-top: 1px solid #e8edf5;
|
|
129
|
+
padding: 10px 0 2px;
|
|
115
130
|
}
|
|
116
131
|
|
|
117
132
|
.section h3 {
|
|
@@ -121,15 +136,19 @@
|
|
|
121
136
|
|
|
122
137
|
.grid-2 {
|
|
123
138
|
display: grid;
|
|
124
|
-
gap:
|
|
139
|
+
gap: 6px;
|
|
125
140
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
126
141
|
}
|
|
127
142
|
|
|
143
|
+
.grid-3 {
|
|
144
|
+
display: grid;
|
|
145
|
+
gap: 6px;
|
|
146
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
147
|
+
}
|
|
148
|
+
|
|
128
149
|
.kv {
|
|
129
|
-
border: 1px
|
|
130
|
-
|
|
131
|
-
padding: 6px 8px;
|
|
132
|
-
background: white;
|
|
150
|
+
border-bottom: 1px dashed #e2e8f2;
|
|
151
|
+
padding: 4px 0 6px;
|
|
133
152
|
}
|
|
134
153
|
|
|
135
154
|
.kv .k {
|
|
@@ -148,12 +167,16 @@
|
|
|
148
167
|
flex-wrap: wrap;
|
|
149
168
|
}
|
|
150
169
|
|
|
170
|
+
.chips.filters {
|
|
171
|
+
margin-top: 2px;
|
|
172
|
+
}
|
|
173
|
+
|
|
151
174
|
.chip {
|
|
152
|
-
border: 1px solid
|
|
153
|
-
border-radius:
|
|
154
|
-
padding: 2px
|
|
175
|
+
border: 1px solid #e0e6f0;
|
|
176
|
+
border-radius: 4px;
|
|
177
|
+
padding: 2px 6px;
|
|
155
178
|
font-size: 12px;
|
|
156
|
-
background: #
|
|
179
|
+
background: #f6f8fc;
|
|
157
180
|
}
|
|
158
181
|
|
|
159
182
|
.chip.ok {
|
|
@@ -162,6 +185,30 @@
|
|
|
162
185
|
background: #effbf4;
|
|
163
186
|
}
|
|
164
187
|
|
|
188
|
+
.icon-row {
|
|
189
|
+
display: flex;
|
|
190
|
+
gap: 6px;
|
|
191
|
+
margin-top: 2px;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.icon-badge {
|
|
195
|
+
display: inline-flex;
|
|
196
|
+
align-items: center;
|
|
197
|
+
justify-content: center;
|
|
198
|
+
width: 18px;
|
|
199
|
+
height: 18px;
|
|
200
|
+
border: 1px solid #e0e6f0;
|
|
201
|
+
border-radius: 4px;
|
|
202
|
+
font-size: 11px;
|
|
203
|
+
background: #f6f8fc;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.icon-badge.warn {
|
|
207
|
+
border-color: #f2d5d5;
|
|
208
|
+
color: #a02424;
|
|
209
|
+
background: #fff4f4;
|
|
210
|
+
}
|
|
211
|
+
|
|
165
212
|
.empty {
|
|
166
213
|
color: var(--muted);
|
|
167
214
|
}
|
|
@@ -171,11 +218,9 @@
|
|
|
171
218
|
}
|
|
172
219
|
|
|
173
220
|
.schema-block {
|
|
174
|
-
border: 1px solid
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
margin-bottom: 8px;
|
|
178
|
-
background: white;
|
|
221
|
+
border-top: 1px solid #e8edf5;
|
|
222
|
+
padding: 8px 0 2px;
|
|
223
|
+
margin-bottom: 6px;
|
|
179
224
|
}
|
|
180
225
|
|
|
181
226
|
.schema-block:last-child {
|
|
@@ -189,29 +234,51 @@
|
|
|
189
234
|
|
|
190
235
|
.schema-tree details {
|
|
191
236
|
margin-left: 12px;
|
|
192
|
-
border-left: 1px
|
|
237
|
+
border-left: 1px solid #e8edf5;
|
|
193
238
|
padding-left: 8px;
|
|
194
239
|
}
|
|
195
240
|
|
|
196
241
|
.tree-row {
|
|
197
242
|
display: grid;
|
|
198
|
-
grid-template-columns: 1fr auto
|
|
243
|
+
grid-template-columns: 1fr auto;
|
|
199
244
|
gap: 8px;
|
|
200
245
|
padding: 3px 0;
|
|
201
246
|
align-items: center;
|
|
202
247
|
}
|
|
203
248
|
|
|
249
|
+
.schema-tree summary .tree-row {
|
|
250
|
+
display: inline-grid;
|
|
251
|
+
vertical-align: middle;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.tree-name {
|
|
255
|
+
display: inline-flex;
|
|
256
|
+
align-items: baseline;
|
|
257
|
+
gap: 2px;
|
|
258
|
+
}
|
|
259
|
+
|
|
204
260
|
.tree-col-muted {
|
|
205
261
|
color: var(--muted);
|
|
206
262
|
font-size: 12px;
|
|
207
263
|
}
|
|
208
264
|
|
|
265
|
+
.required-star {
|
|
266
|
+
color: var(--danger);
|
|
267
|
+
font-weight: 700;
|
|
268
|
+
}
|
|
269
|
+
|
|
209
270
|
.tree-pill {
|
|
210
|
-
border: 1px solid
|
|
211
|
-
border-radius:
|
|
271
|
+
border: 1px solid #e0e6f0;
|
|
272
|
+
border-radius: 4px;
|
|
212
273
|
padding: 1px 6px;
|
|
213
274
|
font-size: 11px;
|
|
214
|
-
background: #
|
|
275
|
+
background: #f2f5fa;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
@media (max-width: 720px) {
|
|
279
|
+
.grid-3 {
|
|
280
|
+
grid-template-columns: 1fr;
|
|
281
|
+
}
|
|
215
282
|
}
|
|
216
283
|
</style>
|
|
217
284
|
</head>
|
|
@@ -238,9 +305,24 @@
|
|
|
238
305
|
<input id="regexSearch" type="checkbox" />
|
|
239
306
|
<span>regex</span>
|
|
240
307
|
</label>
|
|
308
|
+
<label class="field-item">
|
|
309
|
+
<span>type match</span>
|
|
310
|
+
<select id="typeMatchMode">
|
|
311
|
+
<option value="contains" selected>contains</option>
|
|
312
|
+
<option value="exact">exact</option>
|
|
313
|
+
</select>
|
|
314
|
+
</label>
|
|
241
315
|
</div>
|
|
242
316
|
|
|
243
317
|
<div id="fieldCheckboxes" class="field-row"></div>
|
|
318
|
+
<div class="field-actions">
|
|
319
|
+
<button id="selectAllFields" type="button">Select all</button>
|
|
320
|
+
<button id="clearAllFields" type="button">Clear all</button>
|
|
321
|
+
<button id="schemasOnlyFields" type="button">Schemas only</button>
|
|
322
|
+
<button id="metadataOnlyFields" type="button">Metadata only</button>
|
|
323
|
+
<button id="resetFilters" type="button">Reset filters</button>
|
|
324
|
+
</div>
|
|
325
|
+
<div id="activeFilterChips" class="chips filters"></div>
|
|
244
326
|
|
|
245
327
|
<div id="status" class="meta">Load a JSON export to begin.</div>
|
|
246
328
|
</div>
|
|
@@ -264,15 +346,42 @@
|
|
|
264
346
|
{ id: 'tags', label: 'tags', get: (leaf) => leaf.cfg?.tags || [] },
|
|
265
347
|
{ id: 'stability', label: 'stability', get: (leaf) => [leaf.cfg?.stability] },
|
|
266
348
|
{ id: 'docsMeta', label: 'docsMeta', get: (leaf) => [leaf.cfg?.docsMeta] },
|
|
267
|
-
{ id: 'schemas', label: 'schemas', get: (leaf) => [leaf.cfg?.schemas] },
|
|
268
349
|
{
|
|
269
|
-
id: '
|
|
270
|
-
label: '
|
|
271
|
-
get: (leaf, schemaFlatByLeaf) =>
|
|
350
|
+
id: 'types',
|
|
351
|
+
label: 'types',
|
|
352
|
+
get: (leaf, schemaFlatByLeaf) => schemaTypeTokens(schemaFlatByLeaf?.[leaf.key]),
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
id: 'params',
|
|
356
|
+
label: 'params',
|
|
357
|
+
get: (leaf, schemaFlatByLeaf) =>
|
|
358
|
+
schemaSectionToSearchTokens(schemaFlatByLeaf?.[leaf.key], 'params'),
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
id: 'query',
|
|
362
|
+
label: 'query',
|
|
363
|
+
get: (leaf, schemaFlatByLeaf) =>
|
|
364
|
+
schemaSectionToSearchTokens(schemaFlatByLeaf?.[leaf.key], 'query'),
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
id: 'body',
|
|
368
|
+
label: 'body',
|
|
369
|
+
get: (leaf, schemaFlatByLeaf) =>
|
|
370
|
+
schemaSectionToSearchTokens(schemaFlatByLeaf?.[leaf.key], 'body'),
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
id: 'output',
|
|
374
|
+
label: 'output',
|
|
375
|
+
get: (leaf, schemaFlatByLeaf) =>
|
|
376
|
+
schemaSectionToSearchTokens(schemaFlatByLeaf?.[leaf.key], 'output'),
|
|
272
377
|
},
|
|
273
378
|
]
|
|
274
379
|
|
|
275
380
|
const SCHEMA_SECTIONS = ['params', 'query', 'body', 'output']
|
|
381
|
+
const SCHEMA_FIELD_IDS = new Set(SCHEMA_SECTIONS)
|
|
382
|
+
const METADATA_FIELD_IDS = new Set(
|
|
383
|
+
SEARCH_FIELDS.map((field) => field.id).filter((id) => !SCHEMA_FIELD_IDS.has(id)),
|
|
384
|
+
)
|
|
276
385
|
|
|
277
386
|
const state = { payload: null, leaves: [] }
|
|
278
387
|
|
|
@@ -280,10 +389,20 @@
|
|
|
280
389
|
const searchInput = document.getElementById('searchInput')
|
|
281
390
|
const caseSensitiveInput = document.getElementById('caseSensitive')
|
|
282
391
|
const regexSearchInput = document.getElementById('regexSearch')
|
|
392
|
+
const typeMatchModeInput = document.getElementById('typeMatchMode')
|
|
283
393
|
const fieldCheckboxes = document.getElementById('fieldCheckboxes')
|
|
394
|
+
const activeFilterChips = document.getElementById('activeFilterChips')
|
|
395
|
+
const selectAllFieldsBtn = document.getElementById('selectAllFields')
|
|
396
|
+
const clearAllFieldsBtn = document.getElementById('clearAllFields')
|
|
397
|
+
const schemasOnlyFieldsBtn = document.getElementById('schemasOnlyFields')
|
|
398
|
+
const metadataOnlyFieldsBtn = document.getElementById('metadataOnlyFields')
|
|
399
|
+
const resetFiltersBtn = document.getElementById('resetFilters')
|
|
284
400
|
const statusEl = document.getElementById('status')
|
|
285
401
|
const resultsEl = document.getElementById('results')
|
|
286
402
|
|
|
403
|
+
const URL_PARAM_KEY = 'filters'
|
|
404
|
+
let isHydratingFromUrl = false
|
|
405
|
+
|
|
287
406
|
function escapeHtml(value) {
|
|
288
407
|
return String(value)
|
|
289
408
|
.replace(/&/g, '&')
|
|
@@ -305,6 +424,9 @@
|
|
|
305
424
|
if (!query) {
|
|
306
425
|
return {
|
|
307
426
|
active: false,
|
|
427
|
+
query,
|
|
428
|
+
caseSensitive,
|
|
429
|
+
regex,
|
|
308
430
|
error: null,
|
|
309
431
|
test: () => true,
|
|
310
432
|
highlight: (text) => escapeHtml(text ?? ''),
|
|
@@ -317,6 +439,9 @@
|
|
|
317
439
|
const rx = new RegExp(query, flags)
|
|
318
440
|
return {
|
|
319
441
|
active: true,
|
|
442
|
+
query,
|
|
443
|
+
caseSensitive,
|
|
444
|
+
regex,
|
|
320
445
|
error: null,
|
|
321
446
|
test: (text) => {
|
|
322
447
|
const source = String(text ?? '')
|
|
@@ -334,6 +459,9 @@
|
|
|
334
459
|
} catch (error) {
|
|
335
460
|
return {
|
|
336
461
|
active: true,
|
|
462
|
+
query,
|
|
463
|
+
caseSensitive,
|
|
464
|
+
regex,
|
|
337
465
|
error: error instanceof Error ? error.message : 'Invalid regex',
|
|
338
466
|
test: () => false,
|
|
339
467
|
highlight: (text) => escapeHtml(text ?? ''),
|
|
@@ -347,6 +475,9 @@
|
|
|
347
475
|
|
|
348
476
|
return {
|
|
349
477
|
active: true,
|
|
478
|
+
query,
|
|
479
|
+
caseSensitive,
|
|
480
|
+
regex,
|
|
350
481
|
error: null,
|
|
351
482
|
test: (text) => {
|
|
352
483
|
const source = String(text ?? '')
|
|
@@ -370,6 +501,62 @@
|
|
|
370
501
|
return []
|
|
371
502
|
}
|
|
372
503
|
|
|
504
|
+
function flatSchemaToSearchTokens(flatSchema) {
|
|
505
|
+
if (!flatSchema || typeof flatSchema !== 'object') return []
|
|
506
|
+
|
|
507
|
+
const tokens = new Set()
|
|
508
|
+
|
|
509
|
+
Object.entries(flatSchema).forEach(([path, info]) => {
|
|
510
|
+
if (path) {
|
|
511
|
+
tokens.add(path)
|
|
512
|
+
path.split('.').forEach((segment) => {
|
|
513
|
+
if (segment) tokens.add(segment)
|
|
514
|
+
})
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (info && typeof info === 'object') {
|
|
518
|
+
tokens.add(JSON.stringify(info))
|
|
519
|
+
if (info.type) tokens.add(String(info.type))
|
|
520
|
+
if (info.kind) tokens.add(String(info.kind))
|
|
521
|
+
if (info.description) tokens.add(String(info.description))
|
|
522
|
+
if (Array.isArray(info.enumValues)) {
|
|
523
|
+
info.enumValues.forEach((value) => tokens.add(String(value)))
|
|
524
|
+
}
|
|
525
|
+
} else if (info !== null && info !== undefined) {
|
|
526
|
+
tokens.add(String(info))
|
|
527
|
+
}
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
tokens.add(JSON.stringify(flatSchema))
|
|
531
|
+
return Array.from(tokens)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function schemaSectionToSearchTokens(flatSchema, sectionName) {
|
|
535
|
+
if (!flatSchema || typeof flatSchema !== 'object') return []
|
|
536
|
+
const sectionEntries = Object.entries(flatSchema).filter(
|
|
537
|
+
([path]) => path === sectionName || path.startsWith(`${sectionName}.`),
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
if (sectionEntries.length === 0) return []
|
|
541
|
+
return flatSchemaToSearchTokens(Object.fromEntries(sectionEntries))
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function schemaTypeTokens(flatSchema) {
|
|
545
|
+
if (!flatSchema || typeof flatSchema !== 'object') return []
|
|
546
|
+
|
|
547
|
+
const tokens = new Set()
|
|
548
|
+
Object.values(flatSchema).forEach((info) => {
|
|
549
|
+
if (!info || typeof info !== 'object') return
|
|
550
|
+
if (info.type) tokens.add(String(info.type))
|
|
551
|
+
if (info.kind) tokens.add(String(info.kind))
|
|
552
|
+
if (Array.isArray(info.enumValues)) {
|
|
553
|
+
info.enumValues.forEach((value) => tokens.add(String(value)))
|
|
554
|
+
}
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
return Array.from(tokens)
|
|
558
|
+
}
|
|
559
|
+
|
|
373
560
|
function selectedFieldIds() {
|
|
374
561
|
return SEARCH_FIELDS.filter((field) => {
|
|
375
562
|
const input = document.getElementById(`field-${field.id}`)
|
|
@@ -385,6 +572,13 @@
|
|
|
385
572
|
return SEARCH_FIELDS.some((field) => {
|
|
386
573
|
if (!selectedIds.includes(field.id)) return false
|
|
387
574
|
const tokens = toTokens(field.get(leaf, schemaFlatByLeaf))
|
|
575
|
+
if (field.id === 'types' && typeMatchModeInput.value === 'exact' && !engine.regex) {
|
|
576
|
+
const needle = engine.caseSensitive ? engine.query : engine.query.toLowerCase()
|
|
577
|
+
const normalized = engine.caseSensitive
|
|
578
|
+
? tokens
|
|
579
|
+
: tokens.map((token) => String(token).toLowerCase())
|
|
580
|
+
return normalized.includes(needle)
|
|
581
|
+
}
|
|
388
582
|
return tokens.some((token) => engine.test(token))
|
|
389
583
|
})
|
|
390
584
|
}
|
|
@@ -492,6 +686,29 @@
|
|
|
492
686
|
const childKeys = Object.keys(node.children)
|
|
493
687
|
const hasChildren = childKeys.length > 0
|
|
494
688
|
const hasInfo = Boolean(node.info)
|
|
689
|
+
const appendNameCell = (row) => {
|
|
690
|
+
const nameWrap = el('span', 'mono tree-name')
|
|
691
|
+
const nameNode = el('span')
|
|
692
|
+
setHighlighted(nameNode, node.name, engine)
|
|
693
|
+
nameWrap.appendChild(nameNode)
|
|
694
|
+
|
|
695
|
+
if (node.info && !node.info.optional) {
|
|
696
|
+
const star = el('span', 'required-star', '*')
|
|
697
|
+
nameWrap.appendChild(star)
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
if (node.info?.nullable) {
|
|
701
|
+
nameWrap.appendChild(el('span', 'tree-col-muted', '-'))
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
row.appendChild(nameWrap)
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const appendTypeCell = (row, info) => {
|
|
708
|
+
const type = el('span', 'tree-pill')
|
|
709
|
+
setHighlighted(type, info?.type || info?.kind || '—', engine)
|
|
710
|
+
row.appendChild(type)
|
|
711
|
+
}
|
|
495
712
|
|
|
496
713
|
if (isRoot || hasChildren) {
|
|
497
714
|
const details = el('details')
|
|
@@ -499,21 +716,8 @@
|
|
|
499
716
|
const summary = el('summary')
|
|
500
717
|
const row = el('div', 'tree-row')
|
|
501
718
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
row.appendChild(nameNode)
|
|
505
|
-
|
|
506
|
-
if (hasInfo) {
|
|
507
|
-
const type = el('span', 'tree-pill')
|
|
508
|
-
setHighlighted(type, node.info.type, engine)
|
|
509
|
-
row.appendChild(type)
|
|
510
|
-
row.appendChild(el('span', 'tree-col-muted', node.info.optional ? 'optional' : 'required'))
|
|
511
|
-
row.appendChild(el('span', 'tree-col-muted', node.info.nullable ? 'nullable' : 'non-null'))
|
|
512
|
-
} else {
|
|
513
|
-
row.appendChild(el('span'))
|
|
514
|
-
row.appendChild(el('span'))
|
|
515
|
-
row.appendChild(el('span'))
|
|
516
|
-
}
|
|
719
|
+
appendNameCell(row)
|
|
720
|
+
appendTypeCell(row, hasInfo ? node.info : null)
|
|
517
721
|
|
|
518
722
|
summary.appendChild(row)
|
|
519
723
|
details.appendChild(summary)
|
|
@@ -527,43 +731,37 @@
|
|
|
527
731
|
}
|
|
528
732
|
|
|
529
733
|
const row = el('div', 'tree-row')
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
row.appendChild(nameNode)
|
|
533
|
-
|
|
534
|
-
const type = el('span', 'tree-pill')
|
|
535
|
-
setHighlighted(type, node.info ? node.info.type : '—', engine)
|
|
536
|
-
row.appendChild(type)
|
|
537
|
-
row.appendChild(el('span', 'tree-col-muted', node.info?.optional ? 'optional' : 'required'))
|
|
538
|
-
row.appendChild(el('span', 'tree-col-muted', node.info?.nullable ? 'nullable' : 'non-null'))
|
|
734
|
+
appendNameCell(row)
|
|
735
|
+
appendTypeCell(row, node.info)
|
|
539
736
|
|
|
540
737
|
return row
|
|
541
738
|
}
|
|
542
739
|
|
|
543
740
|
function renderSeparatedSchemas(flatSchema, engine) {
|
|
741
|
+
if (!flatSchema || typeof flatSchema !== 'object') return null
|
|
544
742
|
const section = el('div', 'section')
|
|
545
743
|
section.appendChild(el('h3', '', 'Schemas (separated by section)'))
|
|
546
744
|
|
|
547
745
|
const grouped = splitFlatSchemaBySection(flatSchema)
|
|
746
|
+
let hasAnySchemaEntries = false
|
|
548
747
|
|
|
549
748
|
SCHEMA_SECTIONS.forEach((sectionName) => {
|
|
749
|
+
const entries = grouped[sectionName]
|
|
750
|
+
if (!entries || Object.keys(entries).length === 0) return
|
|
751
|
+
|
|
752
|
+
hasAnySchemaEntries = true
|
|
550
753
|
const block = el('div', 'schema-block')
|
|
551
754
|
const header = el('div', 'schema-header mono')
|
|
552
755
|
setHighlighted(header, sectionName, engine)
|
|
553
756
|
block.appendChild(header)
|
|
554
757
|
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
block.appendChild(el('div', 'empty', 'No entries'))
|
|
558
|
-
} else {
|
|
559
|
-
const tree = buildSchemaTree(entries, sectionName)
|
|
560
|
-
block.appendChild(renderTreeNode(tree, engine, true))
|
|
561
|
-
}
|
|
758
|
+
const tree = buildSchemaTree(entries, sectionName)
|
|
759
|
+
block.appendChild(renderTreeNode(tree, engine, true))
|
|
562
760
|
|
|
563
761
|
section.appendChild(block)
|
|
564
762
|
})
|
|
565
763
|
|
|
566
|
-
return section
|
|
764
|
+
return hasAnySchemaEntries ? section : null
|
|
567
765
|
}
|
|
568
766
|
|
|
569
767
|
function renderLeaf(leaf, engine) {
|
|
@@ -578,60 +776,43 @@
|
|
|
578
776
|
|
|
579
777
|
const overview = el('div', 'section')
|
|
580
778
|
overview.appendChild(el('h3', '', 'Overview'))
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
docGrid.appendChild(kv('description', cfg.description, engine))
|
|
598
|
-
docs.appendChild(docGrid)
|
|
599
|
-
|
|
600
|
-
const tagsRow = el('div', 'chips')
|
|
601
|
-
;(cfg.tags || []).forEach((tag) => {
|
|
602
|
-
const chip = el('span', 'chip')
|
|
603
|
-
setHighlighted(chip, tag, engine)
|
|
604
|
-
tagsRow.appendChild(chip)
|
|
605
|
-
})
|
|
606
|
-
if ((cfg.tags || []).length === 0) {
|
|
607
|
-
tagsRow.appendChild(el('span', 'empty', 'No tags'))
|
|
779
|
+
const topGrid = el('div', 'grid-3')
|
|
780
|
+
topGrid.appendChild(kv('group', cfg.docsGroup, engine))
|
|
781
|
+
topGrid.appendChild(
|
|
782
|
+
kv('tags', cfg.tags && cfg.tags.length > 0 ? cfg.tags.join(', ') : undefined, engine),
|
|
783
|
+
)
|
|
784
|
+
topGrid.appendChild(kv('stability', cfg.stability, engine))
|
|
785
|
+
overview.appendChild(topGrid)
|
|
786
|
+
overview.appendChild(kv('summary', cfg.summary, engine))
|
|
787
|
+
overview.appendChild(kv('description', cfg.description, engine))
|
|
788
|
+
|
|
789
|
+
const iconRow = el('div', 'icon-row')
|
|
790
|
+
if (cfg.feed) {
|
|
791
|
+
const feed = el('span', 'icon-badge')
|
|
792
|
+
feed.title = 'Feed'
|
|
793
|
+
setHighlighted(feed, 'F', engine)
|
|
794
|
+
iconRow.appendChild(feed)
|
|
608
795
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
})
|
|
796
|
+
if (cfg.deprecated) {
|
|
797
|
+
const deprecated = el('span', 'icon-badge warn')
|
|
798
|
+
deprecated.title = 'Deprecated'
|
|
799
|
+
setHighlighted(deprecated, 'D', engine)
|
|
800
|
+
iconRow.appendChild(deprecated)
|
|
615
801
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
schemaGrid.appendChild(renderSchemaSummary('output', schemaObj.output, engine))
|
|
627
|
-
schemaGrid.appendChild(renderSchemaSummary('outputMeta', schemaObj.outputMeta, engine))
|
|
628
|
-
schemaGrid.appendChild(renderSchemaSummary('queryExtension', schemaObj.queryExtension, engine))
|
|
629
|
-
schemas.appendChild(schemaGrid)
|
|
630
|
-
content.appendChild(schemas)
|
|
802
|
+
if (cfg.docsHidden) {
|
|
803
|
+
const hidden = el('span', 'icon-badge warn')
|
|
804
|
+
hidden.title = 'Hidden'
|
|
805
|
+
setHighlighted(hidden, 'H', engine)
|
|
806
|
+
iconRow.appendChild(hidden)
|
|
807
|
+
}
|
|
808
|
+
if (iconRow.childNodes.length > 0) {
|
|
809
|
+
overview.appendChild(iconRow)
|
|
810
|
+
}
|
|
811
|
+
content.appendChild(overview)
|
|
631
812
|
|
|
632
813
|
const files = el('div', 'section')
|
|
633
|
-
files.appendChild(el('h3', '', 'Body Files'))
|
|
634
814
|
if (Array.isArray(cfg.bodyFiles) && cfg.bodyFiles.length > 0) {
|
|
815
|
+
files.appendChild(el('h3', '', 'Body Files'))
|
|
635
816
|
const chips = el('div', 'chips')
|
|
636
817
|
cfg.bodyFiles.forEach((file) => {
|
|
637
818
|
const chip = el('span', 'chip ok')
|
|
@@ -639,12 +820,13 @@
|
|
|
639
820
|
chips.appendChild(chip)
|
|
640
821
|
})
|
|
641
822
|
files.appendChild(chips)
|
|
642
|
-
|
|
643
|
-
files.appendChild(el('div', 'empty', 'No file upload fields.'))
|
|
823
|
+
content.appendChild(files)
|
|
644
824
|
}
|
|
645
|
-
content.appendChild(files)
|
|
646
825
|
|
|
647
|
-
|
|
826
|
+
const separatedSchemas = renderSeparatedSchemas(flatSchema, engine)
|
|
827
|
+
if (separatedSchemas) {
|
|
828
|
+
content.appendChild(separatedSchemas)
|
|
829
|
+
}
|
|
648
830
|
|
|
649
831
|
details.appendChild(content)
|
|
650
832
|
return details
|
|
@@ -660,6 +842,10 @@
|
|
|
660
842
|
statusEl.textContent = `Invalid regex: ${engine.error}`
|
|
661
843
|
resultsEl.innerHTML = ''
|
|
662
844
|
resultsEl.appendChild(el('div', 'empty', 'Fix the regex to continue.'))
|
|
845
|
+
renderActiveFilterChips({
|
|
846
|
+
selectedIds: selectedFieldIds(),
|
|
847
|
+
hasRegexError: true,
|
|
848
|
+
})
|
|
663
849
|
return
|
|
664
850
|
}
|
|
665
851
|
|
|
@@ -668,15 +854,18 @@
|
|
|
668
854
|
|
|
669
855
|
statusEl.textContent = `${filtered.length} / ${state.leaves.length} routes matched.`
|
|
670
856
|
resultsEl.innerHTML = ''
|
|
857
|
+
renderActiveFilterChips({ selectedIds, hasRegexError: false })
|
|
671
858
|
|
|
672
859
|
if (filtered.length === 0) {
|
|
673
860
|
resultsEl.appendChild(el('div', 'empty', 'No matches.'))
|
|
861
|
+
syncFilterStateToUrl()
|
|
674
862
|
return
|
|
675
863
|
}
|
|
676
864
|
|
|
677
865
|
filtered.forEach((leaf) => {
|
|
678
866
|
resultsEl.appendChild(renderLeaf(leaf, engine))
|
|
679
867
|
})
|
|
868
|
+
syncFilterStateToUrl()
|
|
680
869
|
}
|
|
681
870
|
|
|
682
871
|
function renderFieldCheckboxes() {
|
|
@@ -694,6 +883,105 @@
|
|
|
694
883
|
})
|
|
695
884
|
}
|
|
696
885
|
|
|
886
|
+
function setFieldSelection(allowedIds) {
|
|
887
|
+
SEARCH_FIELDS.forEach((field) => {
|
|
888
|
+
const input = document.getElementById(`field-${field.id}`)
|
|
889
|
+
if (!input) return
|
|
890
|
+
input.checked = allowedIds.has(field.id)
|
|
891
|
+
})
|
|
892
|
+
renderResults()
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function resetFiltersToDefault() {
|
|
896
|
+
searchInput.value = ''
|
|
897
|
+
caseSensitiveInput.checked = false
|
|
898
|
+
regexSearchInput.checked = false
|
|
899
|
+
typeMatchModeInput.value = 'contains'
|
|
900
|
+
setFieldSelection(new Set(SEARCH_FIELDS.map((field) => field.id)))
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
function renderActiveFilterChips({ selectedIds, hasRegexError }) {
|
|
904
|
+
activeFilterChips.innerHTML = ''
|
|
905
|
+
const chips = []
|
|
906
|
+
|
|
907
|
+
const searchValue = searchInput.value.trim()
|
|
908
|
+
|
|
909
|
+
if (searchValue) chips.push(`search: ${searchValue}`)
|
|
910
|
+
if (typeMatchModeInput.value === 'exact') chips.push('type mode: exact')
|
|
911
|
+
if (caseSensitiveInput.checked) chips.push('case sensitive')
|
|
912
|
+
if (regexSearchInput.checked) chips.push('regex')
|
|
913
|
+
if (hasRegexError) chips.push('regex error')
|
|
914
|
+
|
|
915
|
+
const allIds = SEARCH_FIELDS.map((field) => field.id)
|
|
916
|
+
if (selectedIds.length !== allIds.length) {
|
|
917
|
+
chips.push(`fields: ${selectedIds.join(', ') || 'none'}`)
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (chips.length === 0) {
|
|
921
|
+
activeFilterChips.appendChild(el('span', 'empty', 'No active filters'))
|
|
922
|
+
return
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
chips.forEach((chipText) => {
|
|
926
|
+
const chip = el('span', 'chip')
|
|
927
|
+
chip.textContent = chipText
|
|
928
|
+
activeFilterChips.appendChild(chip)
|
|
929
|
+
})
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
function collectFilterState() {
|
|
933
|
+
return {
|
|
934
|
+
search: searchInput.value,
|
|
935
|
+
caseSensitive: caseSensitiveInput.checked,
|
|
936
|
+
regex: regexSearchInput.checked,
|
|
937
|
+
typeMatchMode: typeMatchModeInput.value === 'exact' ? 'exact' : 'contains',
|
|
938
|
+
selectedFields: selectedFieldIds(),
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function syncFilterStateToUrl() {
|
|
943
|
+
if (isHydratingFromUrl) return
|
|
944
|
+
|
|
945
|
+
const data = collectFilterState()
|
|
946
|
+
const params = new URLSearchParams(window.location.hash.replace(/^#/, ''))
|
|
947
|
+
const serialized = encodeURIComponent(JSON.stringify(data))
|
|
948
|
+
params.set(URL_PARAM_KEY, serialized)
|
|
949
|
+
const nextHash = params.toString()
|
|
950
|
+
if (window.location.hash !== `#${nextHash}`) {
|
|
951
|
+
window.location.hash = nextHash
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function hydrateFilterStateFromUrl() {
|
|
956
|
+
const params = new URLSearchParams(window.location.hash.replace(/^#/, ''))
|
|
957
|
+
const raw = params.get(URL_PARAM_KEY)
|
|
958
|
+
if (!raw) return
|
|
959
|
+
|
|
960
|
+
try {
|
|
961
|
+
const parsed = JSON.parse(decodeURIComponent(raw))
|
|
962
|
+
isHydratingFromUrl = true
|
|
963
|
+
if (typeof parsed.search === 'string') searchInput.value = parsed.search
|
|
964
|
+
caseSensitiveInput.checked = Boolean(parsed.caseSensitive)
|
|
965
|
+
regexSearchInput.checked = Boolean(parsed.regex)
|
|
966
|
+
if (parsed.typeMatchMode === 'exact' || parsed.typeMatchMode === 'contains') {
|
|
967
|
+
typeMatchModeInput.value = parsed.typeMatchMode
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
if (Array.isArray(parsed.selectedFields)) {
|
|
971
|
+
const allowed = new Set(parsed.selectedFields)
|
|
972
|
+
SEARCH_FIELDS.forEach((field) => {
|
|
973
|
+
const input = document.getElementById(`field-${field.id}`)
|
|
974
|
+
if (!input) return
|
|
975
|
+
input.checked = allowed.has(field.id)
|
|
976
|
+
})
|
|
977
|
+
}
|
|
978
|
+
} catch (error) {
|
|
979
|
+
// Ignore malformed hash state.
|
|
980
|
+
} finally {
|
|
981
|
+
isHydratingFromUrl = false
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
697
985
|
async function handleFile(file) {
|
|
698
986
|
const text = await file.text()
|
|
699
987
|
const parsed = JSON.parse(text)
|
|
@@ -731,9 +1019,20 @@
|
|
|
731
1019
|
searchInput.addEventListener('input', renderResults)
|
|
732
1020
|
caseSensitiveInput.addEventListener('change', renderResults)
|
|
733
1021
|
regexSearchInput.addEventListener('change', renderResults)
|
|
1022
|
+
typeMatchModeInput.addEventListener('change', renderResults)
|
|
1023
|
+
|
|
1024
|
+
selectAllFieldsBtn.addEventListener('click', () =>
|
|
1025
|
+
setFieldSelection(new Set(SEARCH_FIELDS.map((field) => field.id))),
|
|
1026
|
+
)
|
|
1027
|
+
clearAllFieldsBtn.addEventListener('click', () => setFieldSelection(new Set()))
|
|
1028
|
+
schemasOnlyFieldsBtn.addEventListener('click', () => setFieldSelection(SCHEMA_FIELD_IDS))
|
|
1029
|
+
metadataOnlyFieldsBtn.addEventListener('click', () => setFieldSelection(METADATA_FIELD_IDS))
|
|
1030
|
+
resetFiltersBtn.addEventListener('click', resetFiltersToDefault)
|
|
734
1031
|
|
|
735
1032
|
renderFieldCheckboxes()
|
|
1033
|
+
hydrateFilterStateFromUrl()
|
|
736
1034
|
initializeFromBakedPayload()
|
|
1035
|
+
renderResults()
|
|
737
1036
|
</script>
|
|
738
1037
|
</body>
|
|
739
1038
|
</html>
|