@emeryld/rrroutes-contract 2.7.12 → 2.8.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.
- package/README.md +5 -5
- package/dist/index.cjs +9 -1364
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.mjs +8 -1337
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -10
- package/bin/rrroutes-export-finalized-leaves.mjs +0 -14
- package/dist/export/defaultViewerTemplate.d.ts +0 -1
- package/dist/export/exportFinalizedLeaves.cli.d.ts +0 -14
- package/dist/export/exportFinalizedLeaves.d.ts +0 -62
- package/dist/export/extractLeafSourceByAst.d.ts +0 -38
- package/dist/export/flattenSchema.d.ts +0 -12
- package/dist/export/index.d.ts +0 -7
- package/dist/export/schemaIntrospection.d.ts +0 -47
- package/dist/export/serializeLeafContract.d.ts +0 -31
- package/tools/finalized-leaves-viewer.html +0 -1551
|
@@ -1,1551 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>Finalized Leaves Viewer</title>
|
|
7
|
-
<style>
|
|
8
|
-
:root {
|
|
9
|
-
--bg: #212121;
|
|
10
|
-
--surface: #2a2a2a;
|
|
11
|
-
--surface-2: #353535;
|
|
12
|
-
--border: #4a4a4a;
|
|
13
|
-
--text: #fffafa;
|
|
14
|
-
--muted: #c8c2c2;
|
|
15
|
-
--accent: #a764d3;
|
|
16
|
-
--schema-accent: #fbbd23;
|
|
17
|
-
--ok: #1f8f4e;
|
|
18
|
-
--danger: #d12b2b;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
* {
|
|
22
|
-
box-sizing: border-box;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
body {
|
|
26
|
-
margin: 0;
|
|
27
|
-
font-family: 'Iosevka Web', 'SFMono-Regular', Menlo, Consolas, monospace;
|
|
28
|
-
color: var(--text);
|
|
29
|
-
background: var(--bg);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
mark {
|
|
33
|
-
background: #00e5ff;
|
|
34
|
-
color: #0f1416;
|
|
35
|
-
border-radius: 2px;
|
|
36
|
-
padding: 0 1px;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.wrap {
|
|
40
|
-
max-width: 1200px;
|
|
41
|
-
margin: 0 auto;
|
|
42
|
-
padding: 20px;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
.card {
|
|
46
|
-
background: var(--surface);
|
|
47
|
-
border: 1px solid var(--border);
|
|
48
|
-
border-radius: 6px;
|
|
49
|
-
padding: 12px;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
.controls {
|
|
53
|
-
display: grid;
|
|
54
|
-
gap: 14px;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.primary-row {
|
|
58
|
-
display: grid;
|
|
59
|
-
gap: 10px;
|
|
60
|
-
grid-template-columns: minmax(220px, 0.9fr) minmax(300px, 1.4fr) minmax(220px, 1fr);
|
|
61
|
-
align-items: end;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.control-block {
|
|
65
|
-
display: grid;
|
|
66
|
-
gap: 6px;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
.control-label {
|
|
70
|
-
font-size: 12px;
|
|
71
|
-
color: var(--muted);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.quick-toggles {
|
|
75
|
-
display: flex;
|
|
76
|
-
flex-wrap: wrap;
|
|
77
|
-
gap: 8px;
|
|
78
|
-
align-items: center;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.scope-row {
|
|
82
|
-
border-top: 1px solid var(--border);
|
|
83
|
-
padding-top: 12px;
|
|
84
|
-
display: grid;
|
|
85
|
-
gap: 10px;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
.scope-actions {
|
|
89
|
-
display: flex;
|
|
90
|
-
flex-wrap: wrap;
|
|
91
|
-
gap: 8px;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
.scope-groups {
|
|
95
|
-
display: grid;
|
|
96
|
-
gap: 10px;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.scope-group {
|
|
100
|
-
display: grid;
|
|
101
|
-
gap: 6px;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.scope-group-title {
|
|
105
|
-
font-size: 12px;
|
|
106
|
-
color: var(--muted);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
.scope-group-chips {
|
|
110
|
-
display: flex;
|
|
111
|
-
flex-wrap: wrap;
|
|
112
|
-
gap: 6px;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
.field-item {
|
|
116
|
-
display: inline-flex;
|
|
117
|
-
align-items: center;
|
|
118
|
-
gap: 6px;
|
|
119
|
-
padding: 3px 0;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
input[type='text'],
|
|
123
|
-
select {
|
|
124
|
-
width: 100%;
|
|
125
|
-
border: 1px solid var(--border);
|
|
126
|
-
border-radius: 4px;
|
|
127
|
-
padding: 8px 10px;
|
|
128
|
-
font: inherit;
|
|
129
|
-
background: var(--surface-2);
|
|
130
|
-
color: var(--text);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
button {
|
|
134
|
-
border: 1px solid var(--border);
|
|
135
|
-
border-radius: 6px;
|
|
136
|
-
padding: 7px 10px;
|
|
137
|
-
font: inherit;
|
|
138
|
-
background: var(--surface-2);
|
|
139
|
-
color: var(--text);
|
|
140
|
-
cursor: pointer;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
button:hover {
|
|
144
|
-
background: var(--surface-2);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.meta {
|
|
148
|
-
color: var(--muted);
|
|
149
|
-
font-size: 12px;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
#results {
|
|
153
|
-
margin-top: 14px;
|
|
154
|
-
display: grid;
|
|
155
|
-
gap: 0;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
details.leaf {
|
|
159
|
-
border-top: 1px solid var(--border);
|
|
160
|
-
padding: 10px 2px;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
summary {
|
|
164
|
-
cursor: pointer;
|
|
165
|
-
font-weight: 700;
|
|
166
|
-
color: var(--accent);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
.leaf-content {
|
|
170
|
-
display: grid;
|
|
171
|
-
gap: 4px;
|
|
172
|
-
margin-top: 10px;
|
|
173
|
-
margin-left: 16px;
|
|
174
|
-
padding-left: 12px;
|
|
175
|
-
border-left: 2px solid var(--border);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
.section {
|
|
179
|
-
border-top: 1px solid var(--border);
|
|
180
|
-
padding: 10px 0 2px;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
.section h3 {
|
|
184
|
-
margin: 0 0 8px;
|
|
185
|
-
font-size: 13px;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
.grid-2 {
|
|
189
|
-
display: grid;
|
|
190
|
-
gap: 6px;
|
|
191
|
-
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
.grid-3 {
|
|
195
|
-
display: grid;
|
|
196
|
-
gap: 6px;
|
|
197
|
-
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
.kv {
|
|
201
|
-
border-bottom: 1px dashed var(--border);
|
|
202
|
-
padding: 4px 0 6px;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
.kv .k {
|
|
206
|
-
font-size: 11px;
|
|
207
|
-
color: var(--muted);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
.kv .k a {
|
|
211
|
-
color: inherit;
|
|
212
|
-
text-decoration: underline;
|
|
213
|
-
text-underline-offset: 2px;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
.kv .v {
|
|
217
|
-
margin-top: 2px;
|
|
218
|
-
word-break: break-word;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
.chips {
|
|
222
|
-
display: flex;
|
|
223
|
-
gap: 6px;
|
|
224
|
-
flex-wrap: wrap;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
.chips.filters {
|
|
228
|
-
margin-top: 2px;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
.chip {
|
|
232
|
-
border: 1px solid var(--border);
|
|
233
|
-
border-radius: 4px;
|
|
234
|
-
padding: 2px 6px;
|
|
235
|
-
font-size: 12px;
|
|
236
|
-
background: var(--surface-2);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
.chip-btn {
|
|
240
|
-
cursor: pointer;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
.chip.ok {
|
|
244
|
-
border-color: #b5e4c8;
|
|
245
|
-
color: var(--ok);
|
|
246
|
-
background: #effbf4;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
.pill-toggle {
|
|
250
|
-
border: 1px solid var(--border);
|
|
251
|
-
border-radius: 999px;
|
|
252
|
-
padding: 6px 12px;
|
|
253
|
-
font-size: 12px;
|
|
254
|
-
background: var(--surface-2);
|
|
255
|
-
color: var(--text);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
.pill-toggle[aria-pressed='true'] {
|
|
259
|
-
border-color: var(--accent);
|
|
260
|
-
background: rgba(167, 100, 211, 0.25);
|
|
261
|
-
color: #f0d8ff;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
.scope-action {
|
|
265
|
-
font-size: 12px;
|
|
266
|
-
padding: 5px 10px;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
.field-chip {
|
|
270
|
-
font-size: 12px;
|
|
271
|
-
padding: 5px 10px;
|
|
272
|
-
border-radius: 999px;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
.field-chip[aria-pressed='true'] {
|
|
276
|
-
border-color: var(--schema-accent);
|
|
277
|
-
background: rgba(251, 189, 35, 0.22);
|
|
278
|
-
color: #ffe7aa;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
.advanced-panel {
|
|
282
|
-
border-top: 1px solid var(--border);
|
|
283
|
-
padding-top: 10px;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
.advanced-panel > summary {
|
|
287
|
-
color: var(--text);
|
|
288
|
-
font-size: 13px;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
.advanced-content {
|
|
292
|
-
margin-top: 10px;
|
|
293
|
-
display: flex;
|
|
294
|
-
flex-wrap: wrap;
|
|
295
|
-
gap: 12px;
|
|
296
|
-
align-items: center;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
.advanced-content .field-item {
|
|
300
|
-
padding: 0;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
.active-filters-row {
|
|
304
|
-
border-top: 1px solid var(--border);
|
|
305
|
-
padding-top: 10px;
|
|
306
|
-
display: grid;
|
|
307
|
-
gap: 6px;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
.active-filters-title {
|
|
311
|
-
color: var(--muted);
|
|
312
|
-
font-size: 12px;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
.icon-row {
|
|
316
|
-
display: flex;
|
|
317
|
-
gap: 6px;
|
|
318
|
-
margin-top: 2px;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
.icon-badge {
|
|
322
|
-
display: inline-flex;
|
|
323
|
-
align-items: center;
|
|
324
|
-
justify-content: center;
|
|
325
|
-
width: 18px;
|
|
326
|
-
height: 18px;
|
|
327
|
-
border: 1px solid var(--border);
|
|
328
|
-
border-radius: 4px;
|
|
329
|
-
font-size: 11px;
|
|
330
|
-
background: var(--surface-2);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
.icon-badge.feed {
|
|
334
|
-
width: auto;
|
|
335
|
-
min-width: 46px;
|
|
336
|
-
padding: 2px 8px;
|
|
337
|
-
font-weight: 700;
|
|
338
|
-
border-color: var(--schema-accent);
|
|
339
|
-
color: var(--schema-accent);
|
|
340
|
-
background: rgba(251, 189, 35, 0.15);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
.icon-badge.warn {
|
|
344
|
-
border-color: #f2d5d5;
|
|
345
|
-
color: #a02424;
|
|
346
|
-
background: #fff4f4;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
.empty {
|
|
350
|
-
color: var(--muted);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
.mono {
|
|
354
|
-
font-family: inherit;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
.schema-block {
|
|
358
|
-
border-top: 1px solid var(--border);
|
|
359
|
-
padding: 8px 0 2px;
|
|
360
|
-
margin-bottom: 6px;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
.schema-block:last-child {
|
|
364
|
-
margin-bottom: 0;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
.schema-header {
|
|
368
|
-
font-weight: 700;
|
|
369
|
-
margin-bottom: 6px;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
.schema-tree details {
|
|
373
|
-
margin-left: 12px;
|
|
374
|
-
border-left: 1px solid var(--border);
|
|
375
|
-
padding-left: 8px;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
.tree-row {
|
|
379
|
-
display: grid;
|
|
380
|
-
grid-template-columns: 1fr auto;
|
|
381
|
-
gap: 8px;
|
|
382
|
-
padding: 3px 0;
|
|
383
|
-
align-items: center;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
.schema-tree summary .tree-row {
|
|
387
|
-
display: grid;
|
|
388
|
-
vertical-align: middle;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
.schema-tree > .tree-row {
|
|
392
|
-
margin-left: 18px;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
.tree-name {
|
|
396
|
-
display: inline-flex;
|
|
397
|
-
align-items: baseline;
|
|
398
|
-
gap: 2px;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
.tree-col-muted {
|
|
402
|
-
color: var(--muted);
|
|
403
|
-
font-size: 12px;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
.required-star {
|
|
407
|
-
color: var(--danger);
|
|
408
|
-
font-weight: 700;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
.tree-pill {
|
|
412
|
-
border: 1px solid var(--schema-accent);
|
|
413
|
-
border-radius: 4px;
|
|
414
|
-
padding: 1px 6px;
|
|
415
|
-
font-size: 11px;
|
|
416
|
-
background: rgba(251, 189, 35, 0.15);
|
|
417
|
-
color: var(--schema-accent);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
.schema-tree details > summary {
|
|
421
|
-
color: var(--schema-accent);
|
|
422
|
-
list-style: none;
|
|
423
|
-
display: grid;
|
|
424
|
-
grid-template-columns: 12px 1fr;
|
|
425
|
-
align-items: center;
|
|
426
|
-
column-gap: 6px;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
.schema-tree details > summary::-webkit-details-marker {
|
|
430
|
-
display: none;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
.schema-tree details > summary::marker {
|
|
434
|
-
content: '';
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
.schema-tree details > summary::before {
|
|
438
|
-
content: '▸';
|
|
439
|
-
display: inline-block;
|
|
440
|
-
grid-column: 1;
|
|
441
|
-
width: 12px;
|
|
442
|
-
text-align: center;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
.schema-tree details > summary > .tree-row {
|
|
446
|
-
grid-column: 2;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
.schema-tree details[open] > summary::before {
|
|
450
|
-
content: '▾';
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
input[type='checkbox'] {
|
|
454
|
-
accent-color: var(--accent);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
@media (max-width: 720px) {
|
|
458
|
-
.primary-row {
|
|
459
|
-
grid-template-columns: 1fr;
|
|
460
|
-
align-items: stretch;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
.grid-3 {
|
|
464
|
-
grid-template-columns: 1fr;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
</style>
|
|
468
|
-
</head>
|
|
469
|
-
<body>
|
|
470
|
-
<div class="wrap">
|
|
471
|
-
<h1>Finalized Leaves Viewer</h1>
|
|
472
|
-
<div class="card controls">
|
|
473
|
-
<div class="primary-row">
|
|
474
|
-
<label class="control-block">
|
|
475
|
-
<span class="control-label">Load export JSON file</span>
|
|
476
|
-
<input id="fileInput" type="file" accept="application/json,.json" />
|
|
477
|
-
</label>
|
|
478
|
-
<label class="control-block">
|
|
479
|
-
<span class="control-label">Search text</span>
|
|
480
|
-
<input id="searchInput" type="text" placeholder="Type to search..." />
|
|
481
|
-
</label>
|
|
482
|
-
<div class="control-block">
|
|
483
|
-
<span class="control-label">Quick filters</span>
|
|
484
|
-
<div class="quick-toggles">
|
|
485
|
-
<button id="caseSensitive" class="pill-toggle" type="button" aria-pressed="false">
|
|
486
|
-
Case sensitive
|
|
487
|
-
</button>
|
|
488
|
-
<button id="regexSearch" class="pill-toggle" type="button" aria-pressed="false">
|
|
489
|
-
Regex
|
|
490
|
-
</button>
|
|
491
|
-
</div>
|
|
492
|
-
</div>
|
|
493
|
-
</div>
|
|
494
|
-
|
|
495
|
-
<div class="scope-row">
|
|
496
|
-
<div class="scope-actions">
|
|
497
|
-
<button id="selectAllFields" class="scope-action" type="button">All</button>
|
|
498
|
-
<button id="clearAllFields" class="scope-action" type="button">None</button>
|
|
499
|
-
<button id="coreFields" class="scope-action" type="button">Core</button>
|
|
500
|
-
<button id="schemasOnlyFields" class="scope-action" type="button">Schema</button>
|
|
501
|
-
<button id="sourceOnlyFields" class="scope-action" type="button">Source</button>
|
|
502
|
-
</div>
|
|
503
|
-
<div id="fieldChipGroups" class="scope-groups"></div>
|
|
504
|
-
</div>
|
|
505
|
-
|
|
506
|
-
<details class="advanced-panel">
|
|
507
|
-
<summary>Advanced filters</summary>
|
|
508
|
-
<div class="advanced-content">
|
|
509
|
-
<label class="field-item">
|
|
510
|
-
<span>type match</span>
|
|
511
|
-
<select id="typeMatchMode">
|
|
512
|
-
<option value="contains" selected>contains</option>
|
|
513
|
-
<option value="exact">exact</option>
|
|
514
|
-
</select>
|
|
515
|
-
</label>
|
|
516
|
-
<button
|
|
517
|
-
id="schemaRowsMatchOnly"
|
|
518
|
-
class="pill-toggle"
|
|
519
|
-
type="button"
|
|
520
|
-
aria-pressed="false"
|
|
521
|
-
>
|
|
522
|
-
Schema rows match only
|
|
523
|
-
</button>
|
|
524
|
-
<button id="resetFilters" type="button">Reset filters</button>
|
|
525
|
-
</div>
|
|
526
|
-
</details>
|
|
527
|
-
|
|
528
|
-
<div class="active-filters-row">
|
|
529
|
-
<div class="active-filters-title">Active filters</div>
|
|
530
|
-
<div id="activeFilterChips" class="chips filters"></div>
|
|
531
|
-
</div>
|
|
532
|
-
|
|
533
|
-
<div id="status" class="meta">Load a JSON export to begin.</div>
|
|
534
|
-
</div>
|
|
535
|
-
|
|
536
|
-
<div id="results"></div>
|
|
537
|
-
</div>
|
|
538
|
-
|
|
539
|
-
<!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->
|
|
540
|
-
<script>
|
|
541
|
-
const SEARCH_FIELDS = [
|
|
542
|
-
{ id: 'method', label: 'method', get: (leaf) => [leaf.method] },
|
|
543
|
-
{ id: 'path', label: 'path', get: (leaf) => [leaf.path] },
|
|
544
|
-
{ id: 'key', label: 'key', get: (leaf) => [leaf.key] },
|
|
545
|
-
{ id: 'summary', label: 'summary', get: (leaf) => [leaf.cfg?.summary] },
|
|
546
|
-
{
|
|
547
|
-
id: 'description',
|
|
548
|
-
label: 'description',
|
|
549
|
-
get: (leaf) => [leaf.cfg?.description],
|
|
550
|
-
},
|
|
551
|
-
{ id: 'docsGroup', label: 'docsGroup', get: (leaf) => [leaf.cfg?.docsGroup] },
|
|
552
|
-
{ id: 'tags', label: 'tags', get: (leaf) => leaf.cfg?.tags || [] },
|
|
553
|
-
{ id: 'stability', label: 'stability', get: (leaf) => [leaf.cfg?.stability] },
|
|
554
|
-
{ id: 'docsMeta', label: 'docsMeta', get: (leaf) => [leaf.cfg?.docsMeta] },
|
|
555
|
-
{
|
|
556
|
-
id: 'sourceDefinition',
|
|
557
|
-
label: 'source definition',
|
|
558
|
-
get: (leaf, schemaFlatByLeaf, payload) => {
|
|
559
|
-
const source = payload?.sourceByLeaf?.[leaf.key]?.definition
|
|
560
|
-
if (!source) return []
|
|
561
|
-
return [`${source.file}:${source.line}:${source.column}`]
|
|
562
|
-
},
|
|
563
|
-
},
|
|
564
|
-
{
|
|
565
|
-
id: 'sourceSchemas',
|
|
566
|
-
label: 'source schemas',
|
|
567
|
-
get: (leaf, schemaFlatByLeaf, payload) => {
|
|
568
|
-
const schemas = payload?.sourceByLeaf?.[leaf.key]?.schemas
|
|
569
|
-
if (!schemas || typeof schemas !== 'object') return []
|
|
570
|
-
return Object.values(schemas).flatMap((schema) => {
|
|
571
|
-
if (!schema || typeof schema !== 'object') return []
|
|
572
|
-
const tokens = []
|
|
573
|
-
if (schema.sourceName) tokens.push(schema.sourceName)
|
|
574
|
-
if (schema.tag) tokens.push(schema.tag)
|
|
575
|
-
tokens.push(`${schema.file}:${schema.line}:${schema.column}`)
|
|
576
|
-
return tokens
|
|
577
|
-
})
|
|
578
|
-
},
|
|
579
|
-
},
|
|
580
|
-
{
|
|
581
|
-
id: 'types',
|
|
582
|
-
label: 'types',
|
|
583
|
-
get: (leaf, schemaFlatByLeaf) => schemaTypeTokens(schemaFlatByLeaf?.[leaf.key]),
|
|
584
|
-
},
|
|
585
|
-
{
|
|
586
|
-
id: 'params',
|
|
587
|
-
label: 'params',
|
|
588
|
-
get: (leaf, schemaFlatByLeaf) =>
|
|
589
|
-
schemaSectionToSearchTokens(schemaFlatByLeaf?.[leaf.key], 'params'),
|
|
590
|
-
},
|
|
591
|
-
{
|
|
592
|
-
id: 'query',
|
|
593
|
-
label: 'query',
|
|
594
|
-
get: (leaf, schemaFlatByLeaf) =>
|
|
595
|
-
schemaSectionToSearchTokens(schemaFlatByLeaf?.[leaf.key], 'query'),
|
|
596
|
-
},
|
|
597
|
-
{
|
|
598
|
-
id: 'body',
|
|
599
|
-
label: 'body',
|
|
600
|
-
get: (leaf, schemaFlatByLeaf) =>
|
|
601
|
-
schemaSectionToSearchTokens(schemaFlatByLeaf?.[leaf.key], 'body'),
|
|
602
|
-
},
|
|
603
|
-
{
|
|
604
|
-
id: 'output',
|
|
605
|
-
label: 'output',
|
|
606
|
-
get: (leaf, schemaFlatByLeaf) =>
|
|
607
|
-
schemaSectionToSearchTokens(schemaFlatByLeaf?.[leaf.key], 'output'),
|
|
608
|
-
},
|
|
609
|
-
]
|
|
610
|
-
|
|
611
|
-
const SCHEMA_SECTIONS = ['params', 'query', 'body', 'output']
|
|
612
|
-
const SCHEMA_FIELD_IDS = new Set(SCHEMA_SECTIONS)
|
|
613
|
-
const SOURCE_FIELD_IDS = new Set(['sourceDefinition', 'sourceSchemas'])
|
|
614
|
-
const SCHEMA_SCOPE_FIELD_IDS = new Set([...SCHEMA_SECTIONS, 'types'])
|
|
615
|
-
const CORE_FIELD_IDS = new Set(
|
|
616
|
-
SEARCH_FIELDS.map((field) => field.id).filter(
|
|
617
|
-
(id) => !SCHEMA_SCOPE_FIELD_IDS.has(id) && !SOURCE_FIELD_IDS.has(id),
|
|
618
|
-
),
|
|
619
|
-
)
|
|
620
|
-
const FIELD_GROUPS = [
|
|
621
|
-
{
|
|
622
|
-
id: 'core',
|
|
623
|
-
label: 'Core fields',
|
|
624
|
-
fieldIds: SEARCH_FIELDS.filter((field) => CORE_FIELD_IDS.has(field.id)).map(
|
|
625
|
-
(field) => field.id,
|
|
626
|
-
),
|
|
627
|
-
},
|
|
628
|
-
{
|
|
629
|
-
id: 'schema',
|
|
630
|
-
label: 'Schema fields',
|
|
631
|
-
fieldIds: SEARCH_FIELDS.filter((field) => SCHEMA_SCOPE_FIELD_IDS.has(field.id)).map(
|
|
632
|
-
(field) => field.id,
|
|
633
|
-
),
|
|
634
|
-
},
|
|
635
|
-
{
|
|
636
|
-
id: 'source',
|
|
637
|
-
label: 'Source fields',
|
|
638
|
-
fieldIds: SEARCH_FIELDS.filter((field) => SOURCE_FIELD_IDS.has(field.id)).map(
|
|
639
|
-
(field) => field.id,
|
|
640
|
-
),
|
|
641
|
-
},
|
|
642
|
-
]
|
|
643
|
-
|
|
644
|
-
const state = {
|
|
645
|
-
payload: null,
|
|
646
|
-
leaves: [],
|
|
647
|
-
selectedFieldIds: new Set(SEARCH_FIELDS.map((field) => field.id)),
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
const fileInput = document.getElementById('fileInput')
|
|
651
|
-
const searchInput = document.getElementById('searchInput')
|
|
652
|
-
const caseSensitiveToggle = document.getElementById('caseSensitive')
|
|
653
|
-
const regexSearchToggle = document.getElementById('regexSearch')
|
|
654
|
-
const typeMatchModeInput = document.getElementById('typeMatchMode')
|
|
655
|
-
const schemaRowsMatchOnlyToggle = document.getElementById('schemaRowsMatchOnly')
|
|
656
|
-
const fieldChipGroups = document.getElementById('fieldChipGroups')
|
|
657
|
-
const activeFilterChips = document.getElementById('activeFilterChips')
|
|
658
|
-
const selectAllFieldsBtn = document.getElementById('selectAllFields')
|
|
659
|
-
const clearAllFieldsBtn = document.getElementById('clearAllFields')
|
|
660
|
-
const coreFieldsBtn = document.getElementById('coreFields')
|
|
661
|
-
const schemasOnlyFieldsBtn = document.getElementById('schemasOnlyFields')
|
|
662
|
-
const sourceOnlyFieldsBtn = document.getElementById('sourceOnlyFields')
|
|
663
|
-
const resetFiltersBtn = document.getElementById('resetFilters')
|
|
664
|
-
const statusEl = document.getElementById('status')
|
|
665
|
-
const resultsEl = document.getElementById('results')
|
|
666
|
-
|
|
667
|
-
const URL_PARAM_KEY = 'filters'
|
|
668
|
-
let isHydratingFromUrl = false
|
|
669
|
-
|
|
670
|
-
function escapeHtml(value) {
|
|
671
|
-
return String(value)
|
|
672
|
-
.replace(/&/g, '&')
|
|
673
|
-
.replace(/</g, '<')
|
|
674
|
-
.replace(/>/g, '>')
|
|
675
|
-
.replace(/"/g, '"')
|
|
676
|
-
.replace(/'/g, ''')
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
function escapeRegExp(value) {
|
|
680
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
function isPressed(button) {
|
|
684
|
-
return button?.getAttribute('aria-pressed') === 'true'
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
function setPressed(button, pressed) {
|
|
688
|
-
if (!button) return
|
|
689
|
-
button.setAttribute('aria-pressed', pressed ? 'true' : 'false')
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
function togglePressed(button) {
|
|
693
|
-
const next = !isPressed(button)
|
|
694
|
-
setPressed(button, next)
|
|
695
|
-
return next
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
function createSearchEngine(queryRaw, options) {
|
|
699
|
-
const query = queryRaw || ''
|
|
700
|
-
const caseSensitive = Boolean(options.caseSensitive)
|
|
701
|
-
const regex = Boolean(options.regex)
|
|
702
|
-
|
|
703
|
-
if (!query) {
|
|
704
|
-
return {
|
|
705
|
-
active: false,
|
|
706
|
-
query,
|
|
707
|
-
caseSensitive,
|
|
708
|
-
regex,
|
|
709
|
-
error: null,
|
|
710
|
-
test: () => true,
|
|
711
|
-
highlight: (text) => escapeHtml(text ?? ''),
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
if (regex) {
|
|
716
|
-
try {
|
|
717
|
-
const flags = caseSensitive ? 'g' : 'gi'
|
|
718
|
-
const rx = new RegExp(query, flags)
|
|
719
|
-
return {
|
|
720
|
-
active: true,
|
|
721
|
-
query,
|
|
722
|
-
caseSensitive,
|
|
723
|
-
regex,
|
|
724
|
-
error: null,
|
|
725
|
-
test: (text) => {
|
|
726
|
-
const source = String(text ?? '')
|
|
727
|
-
const probe = new RegExp(rx.source, rx.flags)
|
|
728
|
-
return probe.test(source)
|
|
729
|
-
},
|
|
730
|
-
highlight: (text) => {
|
|
731
|
-
const source = String(text ?? '')
|
|
732
|
-
return escapeHtml(source).replace(
|
|
733
|
-
new RegExp(rx.source, rx.flags),
|
|
734
|
-
(m) => `<mark>${m}</mark>`,
|
|
735
|
-
)
|
|
736
|
-
},
|
|
737
|
-
}
|
|
738
|
-
} catch (error) {
|
|
739
|
-
return {
|
|
740
|
-
active: true,
|
|
741
|
-
query,
|
|
742
|
-
caseSensitive,
|
|
743
|
-
regex,
|
|
744
|
-
error: error instanceof Error ? error.message : 'Invalid regex',
|
|
745
|
-
test: () => false,
|
|
746
|
-
highlight: (text) => escapeHtml(text ?? ''),
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
const needle = caseSensitive ? query : query.toLowerCase()
|
|
752
|
-
const flags = caseSensitive ? 'g' : 'gi'
|
|
753
|
-
const safe = escapeRegExp(query)
|
|
754
|
-
|
|
755
|
-
return {
|
|
756
|
-
active: true,
|
|
757
|
-
query,
|
|
758
|
-
caseSensitive,
|
|
759
|
-
regex,
|
|
760
|
-
error: null,
|
|
761
|
-
test: (text) => {
|
|
762
|
-
const source = String(text ?? '')
|
|
763
|
-
const hay = caseSensitive ? source : source.toLowerCase()
|
|
764
|
-
return hay.includes(needle)
|
|
765
|
-
},
|
|
766
|
-
highlight: (text) =>
|
|
767
|
-
escapeHtml(String(text ?? '')).replace(
|
|
768
|
-
new RegExp(safe, flags),
|
|
769
|
-
(m) => `<mark>${m}</mark>`,
|
|
770
|
-
),
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
function toTokens(value) {
|
|
775
|
-
if (value === null || value === undefined) return []
|
|
776
|
-
if (typeof value === 'string') return [value]
|
|
777
|
-
if (typeof value === 'number' || typeof value === 'boolean') return [String(value)]
|
|
778
|
-
if (Array.isArray(value)) return value.flatMap((item) => toTokens(item))
|
|
779
|
-
if (typeof value === 'object') return [JSON.stringify(value)]
|
|
780
|
-
return []
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
function flatSchemaToSearchTokens(flatSchema) {
|
|
784
|
-
if (!flatSchema || typeof flatSchema !== 'object') return []
|
|
785
|
-
|
|
786
|
-
const tokens = new Set()
|
|
787
|
-
|
|
788
|
-
Object.entries(flatSchema).forEach(([path, info]) => {
|
|
789
|
-
if (path) {
|
|
790
|
-
tokens.add(path)
|
|
791
|
-
path.split('.').forEach((segment) => {
|
|
792
|
-
if (segment) tokens.add(segment)
|
|
793
|
-
})
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
if (info && typeof info === 'object') {
|
|
797
|
-
tokens.add(JSON.stringify(info))
|
|
798
|
-
if (info.type) tokens.add(String(info.type))
|
|
799
|
-
if (info.kind) tokens.add(String(info.kind))
|
|
800
|
-
if (info.description) tokens.add(String(info.description))
|
|
801
|
-
if (Array.isArray(info.enumValues)) {
|
|
802
|
-
info.enumValues.forEach((value) => tokens.add(String(value)))
|
|
803
|
-
}
|
|
804
|
-
} else if (info !== null && info !== undefined) {
|
|
805
|
-
tokens.add(String(info))
|
|
806
|
-
}
|
|
807
|
-
})
|
|
808
|
-
|
|
809
|
-
tokens.add(JSON.stringify(flatSchema))
|
|
810
|
-
return Array.from(tokens)
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
function schemaSectionToSearchTokens(flatSchema, sectionName) {
|
|
814
|
-
if (!flatSchema || typeof flatSchema !== 'object') return []
|
|
815
|
-
const sectionEntries = Object.entries(flatSchema).filter(
|
|
816
|
-
([path]) => path === sectionName || path.startsWith(`${sectionName}.`),
|
|
817
|
-
)
|
|
818
|
-
|
|
819
|
-
if (sectionEntries.length === 0) return []
|
|
820
|
-
return flatSchemaToSearchTokens(Object.fromEntries(sectionEntries))
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
function schemaTypeTokens(flatSchema) {
|
|
824
|
-
if (!flatSchema || typeof flatSchema !== 'object') return []
|
|
825
|
-
|
|
826
|
-
const tokens = new Set()
|
|
827
|
-
Object.values(flatSchema).forEach((info) => {
|
|
828
|
-
if (!info || typeof info !== 'object') return
|
|
829
|
-
if (info.type) tokens.add(String(info.type))
|
|
830
|
-
if (info.kind) tokens.add(String(info.kind))
|
|
831
|
-
if (Array.isArray(info.enumValues)) {
|
|
832
|
-
info.enumValues.forEach((value) => tokens.add(String(value)))
|
|
833
|
-
}
|
|
834
|
-
})
|
|
835
|
-
|
|
836
|
-
return Array.from(tokens)
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
function schemaEntryTokens(path, info) {
|
|
840
|
-
const tokens = new Set()
|
|
841
|
-
|
|
842
|
-
if (path) {
|
|
843
|
-
tokens.add(path)
|
|
844
|
-
path.split('.').forEach((segment) => {
|
|
845
|
-
if (segment) tokens.add(segment)
|
|
846
|
-
})
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
if (info && typeof info === 'object') {
|
|
850
|
-
tokens.add(JSON.stringify(info))
|
|
851
|
-
if (info.type) tokens.add(String(info.type))
|
|
852
|
-
if (info.kind) tokens.add(String(info.kind))
|
|
853
|
-
if (info.description) tokens.add(String(info.description))
|
|
854
|
-
if (Array.isArray(info.enumValues)) {
|
|
855
|
-
info.enumValues.forEach((value) => tokens.add(String(value)))
|
|
856
|
-
}
|
|
857
|
-
} else if (info !== null && info !== undefined) {
|
|
858
|
-
tokens.add(String(info))
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
return Array.from(tokens)
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
function schemaEntryMatchesActiveFilter(sectionName, fullPath, info, engine, selectedIds) {
|
|
865
|
-
if (!engine.active) return true
|
|
866
|
-
|
|
867
|
-
let matched = false
|
|
868
|
-
if (selectedIds.includes(sectionName)) {
|
|
869
|
-
matched = schemaEntryTokens(fullPath, info).some((token) => engine.test(token))
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
if (!matched && selectedIds.includes('types')) {
|
|
873
|
-
const typeTokens = []
|
|
874
|
-
if (info && typeof info === 'object') {
|
|
875
|
-
if (info.type) typeTokens.push(String(info.type))
|
|
876
|
-
if (info.kind) typeTokens.push(String(info.kind))
|
|
877
|
-
if (Array.isArray(info.enumValues)) {
|
|
878
|
-
info.enumValues.forEach((value) => typeTokens.push(String(value)))
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
if (typeMatchModeInput.value === 'exact' && !engine.regex) {
|
|
883
|
-
const needle = engine.caseSensitive ? engine.query : engine.query.toLowerCase()
|
|
884
|
-
const normalized = engine.caseSensitive
|
|
885
|
-
? typeTokens
|
|
886
|
-
: typeTokens.map((token) => token.toLowerCase())
|
|
887
|
-
matched = normalized.includes(needle)
|
|
888
|
-
} else {
|
|
889
|
-
matched = typeTokens.some((token) => engine.test(token))
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
return matched
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
function matchesLeaf(leaf, engine, selectedIds) {
|
|
897
|
-
if (!engine.active) return true
|
|
898
|
-
if (selectedIds.length === 0) return false
|
|
899
|
-
const schemaFlatByLeaf = state.payload?.schemaFlatByLeaf || {}
|
|
900
|
-
const payload = state.payload || {}
|
|
901
|
-
|
|
902
|
-
return SEARCH_FIELDS.some((field) => {
|
|
903
|
-
if (!selectedIds.includes(field.id)) return false
|
|
904
|
-
const tokens = toTokens(field.get(leaf, schemaFlatByLeaf, payload))
|
|
905
|
-
if (field.id === 'types' && typeMatchModeInput.value === 'exact' && !engine.regex) {
|
|
906
|
-
const needle = engine.caseSensitive ? engine.query : engine.query.toLowerCase()
|
|
907
|
-
const normalized = engine.caseSensitive
|
|
908
|
-
? tokens
|
|
909
|
-
: tokens.map((token) => String(token).toLowerCase())
|
|
910
|
-
return normalized.includes(needle)
|
|
911
|
-
}
|
|
912
|
-
return tokens.some((token) => engine.test(token))
|
|
913
|
-
})
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
function el(tag, className, text) {
|
|
917
|
-
const node = document.createElement(tag)
|
|
918
|
-
if (className) node.className = className
|
|
919
|
-
if (text !== undefined) node.textContent = text
|
|
920
|
-
return node
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
function setHighlighted(node, text, engine) {
|
|
924
|
-
node.innerHTML = engine.highlight(text ?? '')
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
function kv(key, value, engine) {
|
|
928
|
-
const box = el('div', 'kv')
|
|
929
|
-
box.appendChild(el('div', 'k', key))
|
|
930
|
-
const valueNode = el('div', 'v mono')
|
|
931
|
-
setHighlighted(valueNode, value === undefined ? '—' : String(value), engine)
|
|
932
|
-
box.appendChild(valueNode)
|
|
933
|
-
return box
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
function renderSchemaSummary(name, schema, engine) {
|
|
937
|
-
const row = el('div', 'kv')
|
|
938
|
-
row.appendChild(el('div', 'k', name))
|
|
939
|
-
|
|
940
|
-
const valueNode = el('div', 'v mono')
|
|
941
|
-
if (!schema) {
|
|
942
|
-
setHighlighted(valueNode, 'not defined', engine)
|
|
943
|
-
row.appendChild(valueNode)
|
|
944
|
-
return row
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
const parts = [schema.kind]
|
|
948
|
-
if (schema.optional) parts.push('optional')
|
|
949
|
-
if (schema.nullable) parts.push('nullable')
|
|
950
|
-
if (Array.isArray(schema.enumValues) && schema.enumValues.length > 0) {
|
|
951
|
-
parts.push(`enum: ${schema.enumValues.join('|')}`)
|
|
952
|
-
}
|
|
953
|
-
if (schema.properties) {
|
|
954
|
-
parts.push(`properties: ${Object.keys(schema.properties).length}`)
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
setHighlighted(valueNode, parts.join(' | '), engine)
|
|
958
|
-
row.appendChild(valueNode)
|
|
959
|
-
return row
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
function sourceHref(source) {
|
|
963
|
-
if (!source || !source.file) return undefined
|
|
964
|
-
const normalizedPath = String(source.file).replace(/\\/g, '/')
|
|
965
|
-
const platform =
|
|
966
|
-
(navigator.userAgentData && navigator.userAgentData.platform) ||
|
|
967
|
-
navigator.platform ||
|
|
968
|
-
''
|
|
969
|
-
const isWindows = /win/i.test(platform)
|
|
970
|
-
const vscodePath = isWindows
|
|
971
|
-
? normalizedPath.replace(/^\/+/, '')
|
|
972
|
-
: normalizedPath.startsWith('/')
|
|
973
|
-
? normalizedPath
|
|
974
|
-
: `/${normalizedPath}`
|
|
975
|
-
const line = Number.isFinite(source.line) ? source.line : 1
|
|
976
|
-
const column = Number.isFinite(source.column) ? source.column : 1
|
|
977
|
-
|
|
978
|
-
return `vscode://file/${encodeURI(vscodePath)}:${line}:${column}`
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
function sourceDisplay(source) {
|
|
982
|
-
if (!source || !source.file) return '—'
|
|
983
|
-
const line = Number.isFinite(source.line) ? source.line : 1
|
|
984
|
-
const column = Number.isFinite(source.column) ? source.column : 1
|
|
985
|
-
return `${source.file}:${line}:${column}`
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
function createSourceRow(key, source, engine) {
|
|
989
|
-
const box = el('div', 'kv')
|
|
990
|
-
const keyNode = el('div', 'k')
|
|
991
|
-
const valueNode = el('div', 'v mono')
|
|
992
|
-
|
|
993
|
-
if (!source || !source.file) {
|
|
994
|
-
setHighlighted(keyNode, key, engine)
|
|
995
|
-
box.appendChild(keyNode)
|
|
996
|
-
setHighlighted(valueNode, '—', engine)
|
|
997
|
-
box.appendChild(valueNode)
|
|
998
|
-
return box
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
const href = sourceHref(source)
|
|
1002
|
-
const keyLink = document.createElement('a')
|
|
1003
|
-
keyLink.href = href
|
|
1004
|
-
keyLink.target = '_blank'
|
|
1005
|
-
keyLink.rel = 'noopener noreferrer'
|
|
1006
|
-
keyLink.innerHTML = engine.highlight(key)
|
|
1007
|
-
keyNode.appendChild(keyLink)
|
|
1008
|
-
|
|
1009
|
-
setHighlighted(valueNode, sourceDisplay(source), engine)
|
|
1010
|
-
box.appendChild(keyNode)
|
|
1011
|
-
box.appendChild(valueNode)
|
|
1012
|
-
return box
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
function splitFlatSchemaBySection(flatSchema) {
|
|
1016
|
-
const result = {
|
|
1017
|
-
params: {},
|
|
1018
|
-
query: {},
|
|
1019
|
-
body: {},
|
|
1020
|
-
output: {},
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
if (!flatSchema) return result
|
|
1024
|
-
|
|
1025
|
-
Object.entries(flatSchema).forEach(([path, info]) => {
|
|
1026
|
-
const section = SCHEMA_SECTIONS.find(
|
|
1027
|
-
(name) => path === name || path.startsWith(`${name}.`),
|
|
1028
|
-
)
|
|
1029
|
-
if (!section) return
|
|
1030
|
-
result[section][path] = info
|
|
1031
|
-
})
|
|
1032
|
-
|
|
1033
|
-
return result
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
const SCHEMA_SOURCE_KEYS_BY_SECTION = {
|
|
1037
|
-
params: 'paramsSchema',
|
|
1038
|
-
query: 'querySchema',
|
|
1039
|
-
body: 'bodySchema',
|
|
1040
|
-
output: 'outputSchema',
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
function getSchemaSource(source, sectionName) {
|
|
1044
|
-
const schemaKey = SCHEMA_SOURCE_KEYS_BY_SECTION[sectionName]
|
|
1045
|
-
if (!schemaKey || !source?.schemas || typeof source.schemas !== 'object') {
|
|
1046
|
-
return {
|
|
1047
|
-
schemaKey,
|
|
1048
|
-
sourceValue: undefined,
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
return {
|
|
1053
|
-
schemaKey,
|
|
1054
|
-
sourceValue: source.schemas[schemaKey],
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
function createTreeNode(name = '') {
|
|
1059
|
-
return {
|
|
1060
|
-
name,
|
|
1061
|
-
info: null,
|
|
1062
|
-
fullPath: null,
|
|
1063
|
-
children: {},
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
function buildSchemaTree(entries, sectionName) {
|
|
1068
|
-
const root = createTreeNode(sectionName)
|
|
1069
|
-
Object.entries(entries)
|
|
1070
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
1071
|
-
.forEach(([fullPath, info]) => {
|
|
1072
|
-
const trimmed = fullPath === sectionName ? '' : fullPath.slice(sectionName.length + 1)
|
|
1073
|
-
const segments = trimmed ? trimmed.split('.') : []
|
|
1074
|
-
|
|
1075
|
-
let current = root
|
|
1076
|
-
segments.forEach((segment) => {
|
|
1077
|
-
if (!current.children[segment]) {
|
|
1078
|
-
current.children[segment] = createTreeNode(segment)
|
|
1079
|
-
}
|
|
1080
|
-
current = current.children[segment]
|
|
1081
|
-
})
|
|
1082
|
-
|
|
1083
|
-
current.info = info
|
|
1084
|
-
current.fullPath = fullPath
|
|
1085
|
-
})
|
|
1086
|
-
|
|
1087
|
-
return root
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
function renderTreeNode(node, engine, isRoot) {
|
|
1091
|
-
const childKeys = Object.keys(node.children)
|
|
1092
|
-
const hasChildren = childKeys.length > 0
|
|
1093
|
-
const hasInfo = Boolean(node.info)
|
|
1094
|
-
const appendNameCell = (row) => {
|
|
1095
|
-
const nameWrap = el('span', 'mono tree-name')
|
|
1096
|
-
const nameNode = el('span')
|
|
1097
|
-
setHighlighted(nameNode, node.name, engine)
|
|
1098
|
-
nameWrap.appendChild(nameNode)
|
|
1099
|
-
|
|
1100
|
-
if (node.info && !node.info.optional) {
|
|
1101
|
-
const star = el('span', 'required-star', '*')
|
|
1102
|
-
nameWrap.appendChild(star)
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
if (node.info?.nullable) {
|
|
1106
|
-
nameWrap.appendChild(el('span', 'tree-col-muted', '-'))
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
row.appendChild(nameWrap)
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
const appendTypeCell = (row, info) => {
|
|
1113
|
-
const type = el('span', 'tree-pill')
|
|
1114
|
-
setHighlighted(type, info?.type || info?.kind || '—', engine)
|
|
1115
|
-
row.appendChild(type)
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
if (isRoot || hasChildren) {
|
|
1119
|
-
const details = el('details')
|
|
1120
|
-
details.open = true
|
|
1121
|
-
const summary = el('summary')
|
|
1122
|
-
const row = el('div', 'tree-row')
|
|
1123
|
-
|
|
1124
|
-
appendNameCell(row)
|
|
1125
|
-
appendTypeCell(row, hasInfo ? node.info : null)
|
|
1126
|
-
|
|
1127
|
-
summary.appendChild(row)
|
|
1128
|
-
details.appendChild(summary)
|
|
1129
|
-
|
|
1130
|
-
const container = el('div', 'schema-tree')
|
|
1131
|
-
childKeys
|
|
1132
|
-
.sort((a, b) => a.localeCompare(b))
|
|
1133
|
-
.forEach((key) => container.appendChild(renderTreeNode(node.children[key], engine, false)))
|
|
1134
|
-
details.appendChild(container)
|
|
1135
|
-
return details
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
const row = el('div', 'tree-row')
|
|
1139
|
-
appendNameCell(row)
|
|
1140
|
-
appendTypeCell(row, node.info)
|
|
1141
|
-
|
|
1142
|
-
return row
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
function renderSeparatedSchemas(flatSchema, engine, selectedIds, source) {
|
|
1146
|
-
if (!flatSchema || typeof flatSchema !== 'object') return null
|
|
1147
|
-
const section = el('div', 'section')
|
|
1148
|
-
section.appendChild(el('h3', '', 'Schemas (separated by section)'))
|
|
1149
|
-
|
|
1150
|
-
const grouped = splitFlatSchemaBySection(flatSchema)
|
|
1151
|
-
let hasAnySchemaEntries = false
|
|
1152
|
-
const limitToMatchedRows = isPressed(schemaRowsMatchOnlyToggle) && engine.active
|
|
1153
|
-
|
|
1154
|
-
SCHEMA_SECTIONS.forEach((sectionName) => {
|
|
1155
|
-
const rawEntries = grouped[sectionName]
|
|
1156
|
-
if (!rawEntries || Object.keys(rawEntries).length === 0) return
|
|
1157
|
-
|
|
1158
|
-
const entries = limitToMatchedRows
|
|
1159
|
-
? Object.fromEntries(
|
|
1160
|
-
Object.entries(rawEntries).filter(([fullPath, info]) =>
|
|
1161
|
-
schemaEntryMatchesActiveFilter(sectionName, fullPath, info, engine, selectedIds),
|
|
1162
|
-
),
|
|
1163
|
-
)
|
|
1164
|
-
: rawEntries
|
|
1165
|
-
if (Object.keys(entries).length === 0) return
|
|
1166
|
-
|
|
1167
|
-
hasAnySchemaEntries = true
|
|
1168
|
-
const block = el('div', 'schema-block')
|
|
1169
|
-
const header = el('div', 'schema-header mono')
|
|
1170
|
-
setHighlighted(header, sectionName, engine)
|
|
1171
|
-
block.appendChild(header)
|
|
1172
|
-
|
|
1173
|
-
const schemaSource = getSchemaSource(source, sectionName)
|
|
1174
|
-
if (schemaSource.sourceValue) {
|
|
1175
|
-
block.appendChild(
|
|
1176
|
-
createSourceRow(
|
|
1177
|
-
schemaSource.schemaKey || `${sectionName}Schema`,
|
|
1178
|
-
schemaSource.sourceValue,
|
|
1179
|
-
engine,
|
|
1180
|
-
),
|
|
1181
|
-
)
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
const tree = buildSchemaTree(entries, sectionName)
|
|
1185
|
-
block.appendChild(renderTreeNode(tree, engine, true))
|
|
1186
|
-
|
|
1187
|
-
section.appendChild(block)
|
|
1188
|
-
})
|
|
1189
|
-
|
|
1190
|
-
return hasAnySchemaEntries ? section : null
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
function renderLeaf(leaf, engine, selectedIds) {
|
|
1194
|
-
const details = el('details', 'leaf')
|
|
1195
|
-
const summary = el('summary')
|
|
1196
|
-
setHighlighted(summary, `${String(leaf.method || '').toUpperCase()} ${leaf.path || ''}`, engine)
|
|
1197
|
-
details.appendChild(summary)
|
|
1198
|
-
|
|
1199
|
-
const content = el('div', 'leaf-content')
|
|
1200
|
-
const cfg = leaf.cfg || {}
|
|
1201
|
-
const flatSchema = state.payload?.schemaFlatByLeaf?.[leaf.key]
|
|
1202
|
-
|
|
1203
|
-
const overview = el('div', 'section')
|
|
1204
|
-
overview.appendChild(el('h3', '', 'Overview'))
|
|
1205
|
-
const topGrid = el('div', 'grid-3')
|
|
1206
|
-
topGrid.appendChild(kv('group', cfg.docsGroup, engine))
|
|
1207
|
-
topGrid.appendChild(
|
|
1208
|
-
kv('tags', cfg.tags && cfg.tags.length > 0 ? cfg.tags.join(', ') : undefined, engine),
|
|
1209
|
-
)
|
|
1210
|
-
topGrid.appendChild(kv('stability', cfg.stability, engine))
|
|
1211
|
-
overview.appendChild(topGrid)
|
|
1212
|
-
overview.appendChild(kv('summary', cfg.summary, engine))
|
|
1213
|
-
overview.appendChild(kv('description', cfg.description, engine))
|
|
1214
|
-
|
|
1215
|
-
const iconRow = el('div', 'icon-row')
|
|
1216
|
-
if (cfg.feed) {
|
|
1217
|
-
const feed = el('span', 'icon-badge feed')
|
|
1218
|
-
feed.title = 'Feed endpoint'
|
|
1219
|
-
setHighlighted(feed, 'Feed', engine)
|
|
1220
|
-
iconRow.appendChild(feed)
|
|
1221
|
-
}
|
|
1222
|
-
if (cfg.deprecated) {
|
|
1223
|
-
const deprecated = el('span', 'icon-badge warn')
|
|
1224
|
-
deprecated.title = 'Deprecated'
|
|
1225
|
-
setHighlighted(deprecated, 'D', engine)
|
|
1226
|
-
iconRow.appendChild(deprecated)
|
|
1227
|
-
}
|
|
1228
|
-
if (cfg.docsHidden) {
|
|
1229
|
-
const hidden = el('span', 'icon-badge warn')
|
|
1230
|
-
hidden.title = 'Hidden'
|
|
1231
|
-
setHighlighted(hidden, 'H', engine)
|
|
1232
|
-
iconRow.appendChild(hidden)
|
|
1233
|
-
}
|
|
1234
|
-
if (iconRow.childNodes.length > 0) {
|
|
1235
|
-
overview.appendChild(iconRow)
|
|
1236
|
-
}
|
|
1237
|
-
content.appendChild(overview)
|
|
1238
|
-
|
|
1239
|
-
const files = el('div', 'section')
|
|
1240
|
-
if (Array.isArray(cfg.bodyFiles) && cfg.bodyFiles.length > 0) {
|
|
1241
|
-
files.appendChild(el('h3', '', 'Body Files'))
|
|
1242
|
-
const chips = el('div', 'chips')
|
|
1243
|
-
cfg.bodyFiles.forEach((file) => {
|
|
1244
|
-
const chip = el('span', 'chip ok')
|
|
1245
|
-
setHighlighted(chip, `${file.name} (max ${file.maxCount})`, engine)
|
|
1246
|
-
chips.appendChild(chip)
|
|
1247
|
-
})
|
|
1248
|
-
files.appendChild(chips)
|
|
1249
|
-
content.appendChild(files)
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
const sourceByLeaf = state.payload?.sourceByLeaf || {}
|
|
1253
|
-
const source = sourceByLeaf[leaf.key]
|
|
1254
|
-
if (source?.definition) {
|
|
1255
|
-
overview.appendChild(createSourceRow('definition', source.definition, engine))
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
const separatedSchemas = renderSeparatedSchemas(flatSchema, engine, selectedIds, source)
|
|
1259
|
-
if (separatedSchemas) {
|
|
1260
|
-
content.appendChild(separatedSchemas)
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
details.appendChild(content)
|
|
1264
|
-
return details
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
function renderResults() {
|
|
1268
|
-
const engine = createSearchEngine(searchInput.value.trim(), {
|
|
1269
|
-
caseSensitive: isPressed(caseSensitiveToggle),
|
|
1270
|
-
regex: isPressed(regexSearchToggle),
|
|
1271
|
-
})
|
|
1272
|
-
|
|
1273
|
-
if (engine.error) {
|
|
1274
|
-
statusEl.textContent = `Invalid regex: ${engine.error}`
|
|
1275
|
-
resultsEl.innerHTML = ''
|
|
1276
|
-
resultsEl.appendChild(el('div', 'empty', 'Fix the regex to continue.'))
|
|
1277
|
-
renderActiveFilterChips({
|
|
1278
|
-
selectedIds: selectedFieldIds(),
|
|
1279
|
-
hasRegexError: true,
|
|
1280
|
-
})
|
|
1281
|
-
return
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
const selectedIds = selectedFieldIds()
|
|
1285
|
-
const filtered = state.leaves.filter((leaf) => matchesLeaf(leaf, engine, selectedIds))
|
|
1286
|
-
|
|
1287
|
-
statusEl.textContent = `${filtered.length} / ${state.leaves.length} routes matched.`
|
|
1288
|
-
resultsEl.innerHTML = ''
|
|
1289
|
-
renderActiveFilterChips({ selectedIds, hasRegexError: false })
|
|
1290
|
-
|
|
1291
|
-
if (filtered.length === 0) {
|
|
1292
|
-
resultsEl.appendChild(el('div', 'empty', 'No matches.'))
|
|
1293
|
-
syncFilterStateToUrl()
|
|
1294
|
-
return
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
filtered.forEach((leaf) => {
|
|
1298
|
-
resultsEl.appendChild(renderLeaf(leaf, engine, selectedIds))
|
|
1299
|
-
})
|
|
1300
|
-
syncFilterStateToUrl()
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
function selectedFieldIds() {
|
|
1304
|
-
return SEARCH_FIELDS.filter((field) => state.selectedFieldIds.has(field.id)).map(
|
|
1305
|
-
(field) => field.id,
|
|
1306
|
-
)
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
function renderFieldChips() {
|
|
1310
|
-
fieldChipGroups.innerHTML = ''
|
|
1311
|
-
FIELD_GROUPS.forEach((group) => {
|
|
1312
|
-
const groupWrap = el('div', 'scope-group')
|
|
1313
|
-
groupWrap.appendChild(el('div', 'scope-group-title', group.label))
|
|
1314
|
-
const chipsWrap = el('div', 'scope-group-chips')
|
|
1315
|
-
|
|
1316
|
-
group.fieldIds.forEach((fieldId) => {
|
|
1317
|
-
const field = SEARCH_FIELDS.find((item) => item.id === fieldId)
|
|
1318
|
-
if (!field) return
|
|
1319
|
-
const chip = document.createElement('button')
|
|
1320
|
-
chip.type = 'button'
|
|
1321
|
-
chip.className = 'field-chip'
|
|
1322
|
-
chip.textContent = field.label
|
|
1323
|
-
setPressed(chip, state.selectedFieldIds.has(field.id))
|
|
1324
|
-
chip.addEventListener('click', () => {
|
|
1325
|
-
if (state.selectedFieldIds.has(field.id)) {
|
|
1326
|
-
state.selectedFieldIds.delete(field.id)
|
|
1327
|
-
} else {
|
|
1328
|
-
state.selectedFieldIds.add(field.id)
|
|
1329
|
-
}
|
|
1330
|
-
renderFieldChips()
|
|
1331
|
-
renderResults()
|
|
1332
|
-
})
|
|
1333
|
-
chipsWrap.appendChild(chip)
|
|
1334
|
-
})
|
|
1335
|
-
|
|
1336
|
-
groupWrap.appendChild(chipsWrap)
|
|
1337
|
-
fieldChipGroups.appendChild(groupWrap)
|
|
1338
|
-
})
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
function setFieldSelection(allowedIds, options = {}) {
|
|
1342
|
-
const { rerender = true } = options
|
|
1343
|
-
state.selectedFieldIds = new Set(
|
|
1344
|
-
SEARCH_FIELDS.map((field) => field.id).filter((id) => allowedIds.has(id)),
|
|
1345
|
-
)
|
|
1346
|
-
renderFieldChips()
|
|
1347
|
-
if (rerender) renderResults()
|
|
1348
|
-
}
|
|
1349
|
-
|
|
1350
|
-
function resetFiltersToDefault() {
|
|
1351
|
-
searchInput.value = ''
|
|
1352
|
-
setPressed(caseSensitiveToggle, false)
|
|
1353
|
-
setPressed(regexSearchToggle, false)
|
|
1354
|
-
typeMatchModeInput.value = 'contains'
|
|
1355
|
-
setPressed(schemaRowsMatchOnlyToggle, false)
|
|
1356
|
-
setFieldSelection(new Set(SEARCH_FIELDS.map((field) => field.id)), { rerender: false })
|
|
1357
|
-
renderResults()
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
|
-
function renderActiveFilterChips({ selectedIds, hasRegexError }) {
|
|
1361
|
-
activeFilterChips.innerHTML = ''
|
|
1362
|
-
const searchValue = searchInput.value.trim()
|
|
1363
|
-
const chipModels = []
|
|
1364
|
-
|
|
1365
|
-
if (searchValue) {
|
|
1366
|
-
chipModels.push({
|
|
1367
|
-
text: `search: ${searchValue}`,
|
|
1368
|
-
onClick: () => {
|
|
1369
|
-
searchInput.value = ''
|
|
1370
|
-
renderResults()
|
|
1371
|
-
},
|
|
1372
|
-
})
|
|
1373
|
-
}
|
|
1374
|
-
if (typeMatchModeInput.value === 'exact') {
|
|
1375
|
-
chipModels.push({
|
|
1376
|
-
text: 'type mode: exact',
|
|
1377
|
-
onClick: () => {
|
|
1378
|
-
typeMatchModeInput.value = 'contains'
|
|
1379
|
-
renderResults()
|
|
1380
|
-
},
|
|
1381
|
-
})
|
|
1382
|
-
}
|
|
1383
|
-
if (isPressed(schemaRowsMatchOnlyToggle)) {
|
|
1384
|
-
chipModels.push({
|
|
1385
|
-
text: 'schema rows: matched only',
|
|
1386
|
-
onClick: () => {
|
|
1387
|
-
setPressed(schemaRowsMatchOnlyToggle, false)
|
|
1388
|
-
renderResults()
|
|
1389
|
-
},
|
|
1390
|
-
})
|
|
1391
|
-
}
|
|
1392
|
-
if (isPressed(caseSensitiveToggle)) {
|
|
1393
|
-
chipModels.push({
|
|
1394
|
-
text: 'case sensitive',
|
|
1395
|
-
onClick: () => {
|
|
1396
|
-
setPressed(caseSensitiveToggle, false)
|
|
1397
|
-
renderResults()
|
|
1398
|
-
},
|
|
1399
|
-
})
|
|
1400
|
-
}
|
|
1401
|
-
if (isPressed(regexSearchToggle)) {
|
|
1402
|
-
chipModels.push({
|
|
1403
|
-
text: 'regex',
|
|
1404
|
-
onClick: () => {
|
|
1405
|
-
setPressed(regexSearchToggle, false)
|
|
1406
|
-
renderResults()
|
|
1407
|
-
},
|
|
1408
|
-
})
|
|
1409
|
-
}
|
|
1410
|
-
if (hasRegexError) chipModels.push({ text: 'regex error' })
|
|
1411
|
-
|
|
1412
|
-
const allIds = SEARCH_FIELDS.map((field) => field.id)
|
|
1413
|
-
if (selectedIds.length !== allIds.length) {
|
|
1414
|
-
chipModels.push({
|
|
1415
|
-
text: `fields: ${selectedIds.join(', ') || 'none'}`,
|
|
1416
|
-
onClick: () => setFieldSelection(new Set(allIds)),
|
|
1417
|
-
})
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
if (chipModels.length === 0) {
|
|
1421
|
-
activeFilterChips.appendChild(el('span', 'empty', 'No active filters'))
|
|
1422
|
-
return
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
|
-
chipModels.forEach((chipModel) => {
|
|
1426
|
-
const chip = document.createElement(chipModel.onClick ? 'button' : 'span')
|
|
1427
|
-
chip.className = chipModel.onClick ? 'chip chip-btn' : 'chip'
|
|
1428
|
-
chip.textContent = chipModel.text
|
|
1429
|
-
if (chipModel.onClick) {
|
|
1430
|
-
chip.type = 'button'
|
|
1431
|
-
chip.addEventListener('click', chipModel.onClick)
|
|
1432
|
-
}
|
|
1433
|
-
activeFilterChips.appendChild(chip)
|
|
1434
|
-
})
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
function collectFilterState() {
|
|
1438
|
-
return {
|
|
1439
|
-
search: searchInput.value,
|
|
1440
|
-
caseSensitive: isPressed(caseSensitiveToggle),
|
|
1441
|
-
regex: isPressed(regexSearchToggle),
|
|
1442
|
-
typeMatchMode: typeMatchModeInput.value === 'exact' ? 'exact' : 'contains',
|
|
1443
|
-
schemaRowsMatchOnly: isPressed(schemaRowsMatchOnlyToggle),
|
|
1444
|
-
selectedFields: selectedFieldIds(),
|
|
1445
|
-
}
|
|
1446
|
-
}
|
|
1447
|
-
|
|
1448
|
-
function syncFilterStateToUrl() {
|
|
1449
|
-
if (isHydratingFromUrl) return
|
|
1450
|
-
|
|
1451
|
-
const data = collectFilterState()
|
|
1452
|
-
const params = new URLSearchParams(window.location.hash.replace(/^#/, ''))
|
|
1453
|
-
const serialized = encodeURIComponent(JSON.stringify(data))
|
|
1454
|
-
params.set(URL_PARAM_KEY, serialized)
|
|
1455
|
-
const nextHash = params.toString()
|
|
1456
|
-
if (window.location.hash !== `#${nextHash}`) {
|
|
1457
|
-
window.location.hash = nextHash
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
function hydrateFilterStateFromUrl() {
|
|
1462
|
-
const params = new URLSearchParams(window.location.hash.replace(/^#/, ''))
|
|
1463
|
-
const raw = params.get(URL_PARAM_KEY)
|
|
1464
|
-
if (!raw) return
|
|
1465
|
-
|
|
1466
|
-
try {
|
|
1467
|
-
const parsed = JSON.parse(decodeURIComponent(raw))
|
|
1468
|
-
isHydratingFromUrl = true
|
|
1469
|
-
if (typeof parsed.search === 'string') searchInput.value = parsed.search
|
|
1470
|
-
setPressed(caseSensitiveToggle, Boolean(parsed.caseSensitive))
|
|
1471
|
-
setPressed(regexSearchToggle, Boolean(parsed.regex))
|
|
1472
|
-
if (parsed.typeMatchMode === 'exact' || parsed.typeMatchMode === 'contains') {
|
|
1473
|
-
typeMatchModeInput.value = parsed.typeMatchMode
|
|
1474
|
-
}
|
|
1475
|
-
setPressed(schemaRowsMatchOnlyToggle, Boolean(parsed.schemaRowsMatchOnly))
|
|
1476
|
-
|
|
1477
|
-
if (Array.isArray(parsed.selectedFields)) {
|
|
1478
|
-
setFieldSelection(new Set(parsed.selectedFields), { rerender: false })
|
|
1479
|
-
}
|
|
1480
|
-
} catch (error) {
|
|
1481
|
-
// Ignore malformed hash state.
|
|
1482
|
-
} finally {
|
|
1483
|
-
isHydratingFromUrl = false
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
async function handleFile(file) {
|
|
1488
|
-
const text = await file.text()
|
|
1489
|
-
const parsed = JSON.parse(text)
|
|
1490
|
-
if (!parsed || !Array.isArray(parsed.leaves)) {
|
|
1491
|
-
throw new Error('Invalid export file: expected top-level "leaves" array.')
|
|
1492
|
-
}
|
|
1493
|
-
|
|
1494
|
-
state.payload = parsed
|
|
1495
|
-
state.leaves = parsed.leaves
|
|
1496
|
-
renderResults()
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
function initializeFromBakedPayload() {
|
|
1500
|
-
const baked = window.__FINALIZED_LEAVES_PAYLOAD
|
|
1501
|
-
if (!baked || !Array.isArray(baked.leaves)) return
|
|
1502
|
-
|
|
1503
|
-
state.payload = baked
|
|
1504
|
-
state.leaves = baked.leaves
|
|
1505
|
-
statusEl.textContent = `Loaded baked payload with ${state.leaves.length} routes.`
|
|
1506
|
-
renderResults()
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
fileInput.addEventListener('change', async (event) => {
|
|
1510
|
-
const file = event.target.files?.[0]
|
|
1511
|
-
if (!file) return
|
|
1512
|
-
|
|
1513
|
-
try {
|
|
1514
|
-
await handleFile(file)
|
|
1515
|
-
} catch (error) {
|
|
1516
|
-
statusEl.textContent = error instanceof Error ? error.message : String(error)
|
|
1517
|
-
resultsEl.innerHTML = ''
|
|
1518
|
-
}
|
|
1519
|
-
})
|
|
1520
|
-
|
|
1521
|
-
searchInput.addEventListener('input', renderResults)
|
|
1522
|
-
caseSensitiveToggle.addEventListener('click', () => {
|
|
1523
|
-
togglePressed(caseSensitiveToggle)
|
|
1524
|
-
renderResults()
|
|
1525
|
-
})
|
|
1526
|
-
regexSearchToggle.addEventListener('click', () => {
|
|
1527
|
-
togglePressed(regexSearchToggle)
|
|
1528
|
-
renderResults()
|
|
1529
|
-
})
|
|
1530
|
-
typeMatchModeInput.addEventListener('change', renderResults)
|
|
1531
|
-
schemaRowsMatchOnlyToggle.addEventListener('click', () => {
|
|
1532
|
-
togglePressed(schemaRowsMatchOnlyToggle)
|
|
1533
|
-
renderResults()
|
|
1534
|
-
})
|
|
1535
|
-
|
|
1536
|
-
selectAllFieldsBtn.addEventListener('click', () =>
|
|
1537
|
-
setFieldSelection(new Set(SEARCH_FIELDS.map((field) => field.id))),
|
|
1538
|
-
)
|
|
1539
|
-
clearAllFieldsBtn.addEventListener('click', () => setFieldSelection(new Set()))
|
|
1540
|
-
coreFieldsBtn.addEventListener('click', () => setFieldSelection(CORE_FIELD_IDS))
|
|
1541
|
-
schemasOnlyFieldsBtn.addEventListener('click', () => setFieldSelection(SCHEMA_SCOPE_FIELD_IDS))
|
|
1542
|
-
sourceOnlyFieldsBtn.addEventListener('click', () => setFieldSelection(SOURCE_FIELD_IDS))
|
|
1543
|
-
resetFiltersBtn.addEventListener('click', resetFiltersToDefault)
|
|
1544
|
-
|
|
1545
|
-
renderFieldChips()
|
|
1546
|
-
hydrateFilterStateFromUrl()
|
|
1547
|
-
initializeFromBakedPayload()
|
|
1548
|
-
renderResults()
|
|
1549
|
-
</script>
|
|
1550
|
-
</body>
|
|
1551
|
-
</html>
|