@emeryld/rrroutes-contract 2.7.3 → 2.7.4
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 +397 -75
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,13 @@
|
|
|
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
|
|
|
128
143
|
.kv {
|
|
129
|
-
border: 1px
|
|
130
|
-
|
|
131
|
-
padding: 6px 8px;
|
|
132
|
-
background: white;
|
|
144
|
+
border-bottom: 1px dashed #e2e8f2;
|
|
145
|
+
padding: 4px 0 6px;
|
|
133
146
|
}
|
|
134
147
|
|
|
135
148
|
.kv .k {
|
|
@@ -148,12 +161,16 @@
|
|
|
148
161
|
flex-wrap: wrap;
|
|
149
162
|
}
|
|
150
163
|
|
|
164
|
+
.chips.filters {
|
|
165
|
+
margin-top: 2px;
|
|
166
|
+
}
|
|
167
|
+
|
|
151
168
|
.chip {
|
|
152
|
-
border: 1px solid
|
|
153
|
-
border-radius:
|
|
154
|
-
padding: 2px
|
|
169
|
+
border: 1px solid #e0e6f0;
|
|
170
|
+
border-radius: 4px;
|
|
171
|
+
padding: 2px 6px;
|
|
155
172
|
font-size: 12px;
|
|
156
|
-
background: #
|
|
173
|
+
background: #f6f8fc;
|
|
157
174
|
}
|
|
158
175
|
|
|
159
176
|
.chip.ok {
|
|
@@ -171,11 +188,9 @@
|
|
|
171
188
|
}
|
|
172
189
|
|
|
173
190
|
.schema-block {
|
|
174
|
-
border: 1px solid
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
margin-bottom: 8px;
|
|
178
|
-
background: white;
|
|
191
|
+
border-top: 1px solid #e8edf5;
|
|
192
|
+
padding: 8px 0 2px;
|
|
193
|
+
margin-bottom: 6px;
|
|
179
194
|
}
|
|
180
195
|
|
|
181
196
|
.schema-block:last-child {
|
|
@@ -189,29 +204,45 @@
|
|
|
189
204
|
|
|
190
205
|
.schema-tree details {
|
|
191
206
|
margin-left: 12px;
|
|
192
|
-
border-left: 1px
|
|
207
|
+
border-left: 1px solid #e8edf5;
|
|
193
208
|
padding-left: 8px;
|
|
194
209
|
}
|
|
195
210
|
|
|
196
211
|
.tree-row {
|
|
197
212
|
display: grid;
|
|
198
|
-
grid-template-columns: 1fr auto
|
|
213
|
+
grid-template-columns: 1fr auto;
|
|
199
214
|
gap: 8px;
|
|
200
215
|
padding: 3px 0;
|
|
201
216
|
align-items: center;
|
|
202
217
|
}
|
|
203
218
|
|
|
219
|
+
.schema-tree summary .tree-row {
|
|
220
|
+
display: inline-grid;
|
|
221
|
+
vertical-align: middle;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.tree-name {
|
|
225
|
+
display: inline-flex;
|
|
226
|
+
align-items: baseline;
|
|
227
|
+
gap: 2px;
|
|
228
|
+
}
|
|
229
|
+
|
|
204
230
|
.tree-col-muted {
|
|
205
231
|
color: var(--muted);
|
|
206
232
|
font-size: 12px;
|
|
207
233
|
}
|
|
208
234
|
|
|
235
|
+
.required-star {
|
|
236
|
+
color: var(--danger);
|
|
237
|
+
font-weight: 700;
|
|
238
|
+
}
|
|
239
|
+
|
|
209
240
|
.tree-pill {
|
|
210
|
-
border: 1px solid
|
|
211
|
-
border-radius:
|
|
241
|
+
border: 1px solid #e0e6f0;
|
|
242
|
+
border-radius: 4px;
|
|
212
243
|
padding: 1px 6px;
|
|
213
244
|
font-size: 11px;
|
|
214
|
-
background: #
|
|
245
|
+
background: #f2f5fa;
|
|
215
246
|
}
|
|
216
247
|
</style>
|
|
217
248
|
</head>
|
|
@@ -229,6 +260,11 @@
|
|
|
229
260
|
<input id="searchInput" type="text" placeholder="Type to search..." />
|
|
230
261
|
</label>
|
|
231
262
|
|
|
263
|
+
<label>
|
|
264
|
+
Type filter:
|
|
265
|
+
<input id="typeFilterInput" type="text" placeholder='Type/kind/enum (e.g. "paid")' />
|
|
266
|
+
</label>
|
|
267
|
+
|
|
232
268
|
<div class="field-row">
|
|
233
269
|
<label class="field-item">
|
|
234
270
|
<input id="caseSensitive" type="checkbox" />
|
|
@@ -238,9 +274,28 @@
|
|
|
238
274
|
<input id="regexSearch" type="checkbox" />
|
|
239
275
|
<span>regex</span>
|
|
240
276
|
</label>
|
|
277
|
+
<label class="field-item">
|
|
278
|
+
<input id="hasEnumOnly" type="checkbox" />
|
|
279
|
+
<span>has enum only</span>
|
|
280
|
+
</label>
|
|
281
|
+
<label class="field-item">
|
|
282
|
+
<span>type match</span>
|
|
283
|
+
<select id="typeMatchMode">
|
|
284
|
+
<option value="contains" selected>contains</option>
|
|
285
|
+
<option value="exact">exact</option>
|
|
286
|
+
</select>
|
|
287
|
+
</label>
|
|
241
288
|
</div>
|
|
242
289
|
|
|
243
290
|
<div id="fieldCheckboxes" class="field-row"></div>
|
|
291
|
+
<div class="field-actions">
|
|
292
|
+
<button id="selectAllFields" type="button">Select all</button>
|
|
293
|
+
<button id="clearAllFields" type="button">Clear all</button>
|
|
294
|
+
<button id="schemasOnlyFields" type="button">Schemas only</button>
|
|
295
|
+
<button id="metadataOnlyFields" type="button">Metadata only</button>
|
|
296
|
+
<button id="resetFilters" type="button">Reset filters</button>
|
|
297
|
+
</div>
|
|
298
|
+
<div id="activeFilterChips" class="chips filters"></div>
|
|
244
299
|
|
|
245
300
|
<div id="status" class="meta">Load a JSON export to begin.</div>
|
|
246
301
|
</div>
|
|
@@ -264,26 +319,60 @@
|
|
|
264
319
|
{ id: 'tags', label: 'tags', get: (leaf) => leaf.cfg?.tags || [] },
|
|
265
320
|
{ id: 'stability', label: 'stability', get: (leaf) => [leaf.cfg?.stability] },
|
|
266
321
|
{ id: 'docsMeta', label: 'docsMeta', get: (leaf) => [leaf.cfg?.docsMeta] },
|
|
267
|
-
{ id: 'schemas', label: 'schemas', get: (leaf) => [leaf.cfg?.schemas] },
|
|
268
322
|
{
|
|
269
|
-
id: '
|
|
270
|
-
label: '
|
|
271
|
-
get: (leaf, schemaFlatByLeaf) =>
|
|
323
|
+
id: 'params',
|
|
324
|
+
label: 'params',
|
|
325
|
+
get: (leaf, schemaFlatByLeaf) =>
|
|
326
|
+
schemaSectionToSearchTokens(schemaFlatByLeaf?.[leaf.key], 'params'),
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
id: 'query',
|
|
330
|
+
label: 'query',
|
|
331
|
+
get: (leaf, schemaFlatByLeaf) =>
|
|
332
|
+
schemaSectionToSearchTokens(schemaFlatByLeaf?.[leaf.key], 'query'),
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
id: 'body',
|
|
336
|
+
label: 'body',
|
|
337
|
+
get: (leaf, schemaFlatByLeaf) =>
|
|
338
|
+
schemaSectionToSearchTokens(schemaFlatByLeaf?.[leaf.key], 'body'),
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
id: 'output',
|
|
342
|
+
label: 'output',
|
|
343
|
+
get: (leaf, schemaFlatByLeaf) =>
|
|
344
|
+
schemaSectionToSearchTokens(schemaFlatByLeaf?.[leaf.key], 'output'),
|
|
272
345
|
},
|
|
273
346
|
]
|
|
274
347
|
|
|
275
348
|
const SCHEMA_SECTIONS = ['params', 'query', 'body', 'output']
|
|
349
|
+
const SCHEMA_FIELD_IDS = new Set(SCHEMA_SECTIONS)
|
|
350
|
+
const METADATA_FIELD_IDS = new Set(
|
|
351
|
+
SEARCH_FIELDS.map((field) => field.id).filter((id) => !SCHEMA_FIELD_IDS.has(id)),
|
|
352
|
+
)
|
|
276
353
|
|
|
277
354
|
const state = { payload: null, leaves: [] }
|
|
278
355
|
|
|
279
356
|
const fileInput = document.getElementById('fileInput')
|
|
280
357
|
const searchInput = document.getElementById('searchInput')
|
|
358
|
+
const typeFilterInput = document.getElementById('typeFilterInput')
|
|
281
359
|
const caseSensitiveInput = document.getElementById('caseSensitive')
|
|
282
360
|
const regexSearchInput = document.getElementById('regexSearch')
|
|
361
|
+
const hasEnumOnlyInput = document.getElementById('hasEnumOnly')
|
|
362
|
+
const typeMatchModeInput = document.getElementById('typeMatchMode')
|
|
283
363
|
const fieldCheckboxes = document.getElementById('fieldCheckboxes')
|
|
364
|
+
const activeFilterChips = document.getElementById('activeFilterChips')
|
|
365
|
+
const selectAllFieldsBtn = document.getElementById('selectAllFields')
|
|
366
|
+
const clearAllFieldsBtn = document.getElementById('clearAllFields')
|
|
367
|
+
const schemasOnlyFieldsBtn = document.getElementById('schemasOnlyFields')
|
|
368
|
+
const metadataOnlyFieldsBtn = document.getElementById('metadataOnlyFields')
|
|
369
|
+
const resetFiltersBtn = document.getElementById('resetFilters')
|
|
284
370
|
const statusEl = document.getElementById('status')
|
|
285
371
|
const resultsEl = document.getElementById('results')
|
|
286
372
|
|
|
373
|
+
const URL_PARAM_KEY = 'filters'
|
|
374
|
+
let isHydratingFromUrl = false
|
|
375
|
+
|
|
287
376
|
function escapeHtml(value) {
|
|
288
377
|
return String(value)
|
|
289
378
|
.replace(/&/g, '&')
|
|
@@ -370,6 +459,65 @@
|
|
|
370
459
|
return []
|
|
371
460
|
}
|
|
372
461
|
|
|
462
|
+
function flatSchemaToSearchTokens(flatSchema) {
|
|
463
|
+
if (!flatSchema || typeof flatSchema !== 'object') return []
|
|
464
|
+
|
|
465
|
+
const tokens = new Set()
|
|
466
|
+
|
|
467
|
+
Object.entries(flatSchema).forEach(([path, info]) => {
|
|
468
|
+
if (path) {
|
|
469
|
+
tokens.add(path)
|
|
470
|
+
path.split('.').forEach((segment) => {
|
|
471
|
+
if (segment) tokens.add(segment)
|
|
472
|
+
})
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (info && typeof info === 'object') {
|
|
476
|
+
tokens.add(JSON.stringify(info))
|
|
477
|
+
if (info.type) tokens.add(String(info.type))
|
|
478
|
+
if (info.kind) tokens.add(String(info.kind))
|
|
479
|
+
if (info.description) tokens.add(String(info.description))
|
|
480
|
+
if (Array.isArray(info.enumValues)) {
|
|
481
|
+
info.enumValues.forEach((value) => tokens.add(String(value)))
|
|
482
|
+
}
|
|
483
|
+
} else if (info !== null && info !== undefined) {
|
|
484
|
+
tokens.add(String(info))
|
|
485
|
+
}
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
tokens.add(JSON.stringify(flatSchema))
|
|
489
|
+
return Array.from(tokens)
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function schemaSectionToSearchTokens(flatSchema, sectionName) {
|
|
493
|
+
if (!flatSchema || typeof flatSchema !== 'object') return []
|
|
494
|
+
const sectionEntries = Object.entries(flatSchema).filter(
|
|
495
|
+
([path]) => path === sectionName || path.startsWith(`${sectionName}.`),
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
if (sectionEntries.length === 0) return []
|
|
499
|
+
return flatSchemaToSearchTokens(Object.fromEntries(sectionEntries))
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function typeFilterTokensByLeaf(flatSchema) {
|
|
503
|
+
if (!flatSchema || typeof flatSchema !== 'object') return { tokens: [], hasEnum: false }
|
|
504
|
+
|
|
505
|
+
const tokens = new Set()
|
|
506
|
+
let hasEnum = false
|
|
507
|
+
|
|
508
|
+
Object.values(flatSchema).forEach((info) => {
|
|
509
|
+
if (!info || typeof info !== 'object') return
|
|
510
|
+
if (info.type) tokens.add(String(info.type))
|
|
511
|
+
if (info.kind) tokens.add(String(info.kind))
|
|
512
|
+
if (Array.isArray(info.enumValues) && info.enumValues.length > 0) {
|
|
513
|
+
hasEnum = true
|
|
514
|
+
info.enumValues.forEach((value) => tokens.add(String(value)))
|
|
515
|
+
}
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
return { tokens: Array.from(tokens), hasEnum }
|
|
519
|
+
}
|
|
520
|
+
|
|
373
521
|
function selectedFieldIds() {
|
|
374
522
|
return SEARCH_FIELDS.filter((field) => {
|
|
375
523
|
const input = document.getElementById(`field-${field.id}`)
|
|
@@ -389,6 +537,42 @@
|
|
|
389
537
|
})
|
|
390
538
|
}
|
|
391
539
|
|
|
540
|
+
function matchesTypeFilter(leaf, typeEngine, matchMode, hasEnumOnly) {
|
|
541
|
+
const schemaFlatByLeaf = state.payload?.schemaFlatByLeaf || {}
|
|
542
|
+
const { tokens, hasEnum } = typeFilterTokensByLeaf(schemaFlatByLeaf?.[leaf.key])
|
|
543
|
+
|
|
544
|
+
if (hasEnumOnly && !hasEnum) return false
|
|
545
|
+
if (!typeEngine.active) return true
|
|
546
|
+
|
|
547
|
+
if (matchMode === 'exact') {
|
|
548
|
+
const sourceTokens = typeEngine.caseSensitive
|
|
549
|
+
? tokens
|
|
550
|
+
: tokens.map((token) => token.toLowerCase())
|
|
551
|
+
const needle = typeEngine.caseSensitive ? typeEngine.query : typeEngine.query.toLowerCase()
|
|
552
|
+
return sourceTokens.includes(needle)
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return tokens.some((token) => typeEngine.test(token))
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function createTypeSearchEngine(queryRaw) {
|
|
559
|
+
const query = queryRaw || ''
|
|
560
|
+
const caseSensitive = Boolean(caseSensitiveInput.checked)
|
|
561
|
+
if (!query) return { active: false, query, caseSensitive, test: () => true }
|
|
562
|
+
|
|
563
|
+
const needle = caseSensitive ? query : query.toLowerCase()
|
|
564
|
+
return {
|
|
565
|
+
active: true,
|
|
566
|
+
query,
|
|
567
|
+
caseSensitive,
|
|
568
|
+
test: (text) => {
|
|
569
|
+
const source = String(text ?? '')
|
|
570
|
+
const hay = caseSensitive ? source : source.toLowerCase()
|
|
571
|
+
return hay.includes(needle)
|
|
572
|
+
},
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
392
576
|
function el(tag, className, text) {
|
|
393
577
|
const node = document.createElement(tag)
|
|
394
578
|
if (className) node.className = className
|
|
@@ -492,6 +676,29 @@
|
|
|
492
676
|
const childKeys = Object.keys(node.children)
|
|
493
677
|
const hasChildren = childKeys.length > 0
|
|
494
678
|
const hasInfo = Boolean(node.info)
|
|
679
|
+
const appendNameCell = (row) => {
|
|
680
|
+
const nameWrap = el('span', 'mono tree-name')
|
|
681
|
+
const nameNode = el('span')
|
|
682
|
+
setHighlighted(nameNode, node.name, engine)
|
|
683
|
+
nameWrap.appendChild(nameNode)
|
|
684
|
+
|
|
685
|
+
if (node.info && !node.info.optional) {
|
|
686
|
+
const star = el('span', 'required-star', '*')
|
|
687
|
+
nameWrap.appendChild(star)
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (node.info?.nullable) {
|
|
691
|
+
nameWrap.appendChild(el('span', 'tree-col-muted', '-'))
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
row.appendChild(nameWrap)
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const appendTypeCell = (row, info) => {
|
|
698
|
+
const type = el('span', 'tree-pill')
|
|
699
|
+
setHighlighted(type, info?.type || info?.kind || '—', engine)
|
|
700
|
+
row.appendChild(type)
|
|
701
|
+
}
|
|
495
702
|
|
|
496
703
|
if (isRoot || hasChildren) {
|
|
497
704
|
const details = el('details')
|
|
@@ -499,21 +706,8 @@
|
|
|
499
706
|
const summary = el('summary')
|
|
500
707
|
const row = el('div', 'tree-row')
|
|
501
708
|
|
|
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
|
-
}
|
|
709
|
+
appendNameCell(row)
|
|
710
|
+
appendTypeCell(row, hasInfo ? node.info : null)
|
|
517
711
|
|
|
518
712
|
summary.appendChild(row)
|
|
519
713
|
details.appendChild(summary)
|
|
@@ -527,15 +721,8 @@
|
|
|
527
721
|
}
|
|
528
722
|
|
|
529
723
|
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'))
|
|
724
|
+
appendNameCell(row)
|
|
725
|
+
appendTypeCell(row, node.info)
|
|
539
726
|
|
|
540
727
|
return row
|
|
541
728
|
}
|
|
@@ -655,28 +842,42 @@
|
|
|
655
842
|
caseSensitive: caseSensitiveInput.checked,
|
|
656
843
|
regex: regexSearchInput.checked,
|
|
657
844
|
})
|
|
845
|
+
const typeEngine = createTypeSearchEngine(typeFilterInput.value.trim())
|
|
846
|
+
const hasEnumOnly = Boolean(hasEnumOnlyInput.checked)
|
|
847
|
+
const typeMatchMode = typeMatchModeInput.value === 'exact' ? 'exact' : 'contains'
|
|
658
848
|
|
|
659
849
|
if (engine.error) {
|
|
660
850
|
statusEl.textContent = `Invalid regex: ${engine.error}`
|
|
661
851
|
resultsEl.innerHTML = ''
|
|
662
852
|
resultsEl.appendChild(el('div', 'empty', 'Fix the regex to continue.'))
|
|
853
|
+
renderActiveFilterChips({
|
|
854
|
+
selectedIds: selectedFieldIds(),
|
|
855
|
+
hasRegexError: true,
|
|
856
|
+
})
|
|
663
857
|
return
|
|
664
858
|
}
|
|
665
859
|
|
|
666
860
|
const selectedIds = selectedFieldIds()
|
|
667
|
-
const filtered = state.leaves.filter(
|
|
861
|
+
const filtered = state.leaves.filter(
|
|
862
|
+
(leaf) =>
|
|
863
|
+
matchesLeaf(leaf, engine, selectedIds) &&
|
|
864
|
+
matchesTypeFilter(leaf, typeEngine, typeMatchMode, hasEnumOnly),
|
|
865
|
+
)
|
|
668
866
|
|
|
669
867
|
statusEl.textContent = `${filtered.length} / ${state.leaves.length} routes matched.`
|
|
670
868
|
resultsEl.innerHTML = ''
|
|
869
|
+
renderActiveFilterChips({ selectedIds, hasRegexError: false })
|
|
671
870
|
|
|
672
871
|
if (filtered.length === 0) {
|
|
673
872
|
resultsEl.appendChild(el('div', 'empty', 'No matches.'))
|
|
873
|
+
syncFilterStateToUrl()
|
|
674
874
|
return
|
|
675
875
|
}
|
|
676
876
|
|
|
677
877
|
filtered.forEach((leaf) => {
|
|
678
878
|
resultsEl.appendChild(renderLeaf(leaf, engine))
|
|
679
879
|
})
|
|
880
|
+
syncFilterStateToUrl()
|
|
680
881
|
}
|
|
681
882
|
|
|
682
883
|
function renderFieldCheckboxes() {
|
|
@@ -694,6 +895,114 @@
|
|
|
694
895
|
})
|
|
695
896
|
}
|
|
696
897
|
|
|
898
|
+
function setFieldSelection(allowedIds) {
|
|
899
|
+
SEARCH_FIELDS.forEach((field) => {
|
|
900
|
+
const input = document.getElementById(`field-${field.id}`)
|
|
901
|
+
if (!input) return
|
|
902
|
+
input.checked = allowedIds.has(field.id)
|
|
903
|
+
})
|
|
904
|
+
renderResults()
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function resetFiltersToDefault() {
|
|
908
|
+
searchInput.value = ''
|
|
909
|
+
typeFilterInput.value = ''
|
|
910
|
+
caseSensitiveInput.checked = false
|
|
911
|
+
regexSearchInput.checked = false
|
|
912
|
+
hasEnumOnlyInput.checked = false
|
|
913
|
+
typeMatchModeInput.value = 'contains'
|
|
914
|
+
setFieldSelection(new Set(SEARCH_FIELDS.map((field) => field.id)))
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function renderActiveFilterChips({ selectedIds, hasRegexError }) {
|
|
918
|
+
activeFilterChips.innerHTML = ''
|
|
919
|
+
const chips = []
|
|
920
|
+
|
|
921
|
+
const searchValue = searchInput.value.trim()
|
|
922
|
+
const typeValue = typeFilterInput.value.trim()
|
|
923
|
+
|
|
924
|
+
if (searchValue) chips.push(`search: ${searchValue}`)
|
|
925
|
+
if (typeValue) chips.push(`type: ${typeValue}`)
|
|
926
|
+
if (typeMatchModeInput.value === 'exact') chips.push('type mode: exact')
|
|
927
|
+
if (caseSensitiveInput.checked) chips.push('case sensitive')
|
|
928
|
+
if (regexSearchInput.checked) chips.push('regex')
|
|
929
|
+
if (hasEnumOnlyInput.checked) chips.push('has enum only')
|
|
930
|
+
if (hasRegexError) chips.push('regex error')
|
|
931
|
+
|
|
932
|
+
const allIds = SEARCH_FIELDS.map((field) => field.id)
|
|
933
|
+
if (selectedIds.length !== allIds.length) {
|
|
934
|
+
chips.push(`fields: ${selectedIds.join(', ') || 'none'}`)
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
if (chips.length === 0) {
|
|
938
|
+
activeFilterChips.appendChild(el('span', 'empty', 'No active filters'))
|
|
939
|
+
return
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
chips.forEach((chipText) => {
|
|
943
|
+
const chip = el('span', 'chip')
|
|
944
|
+
chip.textContent = chipText
|
|
945
|
+
activeFilterChips.appendChild(chip)
|
|
946
|
+
})
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
function collectFilterState() {
|
|
950
|
+
return {
|
|
951
|
+
search: searchInput.value,
|
|
952
|
+
typeFilter: typeFilterInput.value,
|
|
953
|
+
caseSensitive: caseSensitiveInput.checked,
|
|
954
|
+
regex: regexSearchInput.checked,
|
|
955
|
+
hasEnumOnly: hasEnumOnlyInput.checked,
|
|
956
|
+
typeMatchMode: typeMatchModeInput.value === 'exact' ? 'exact' : 'contains',
|
|
957
|
+
selectedFields: selectedFieldIds(),
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function syncFilterStateToUrl() {
|
|
962
|
+
if (isHydratingFromUrl) return
|
|
963
|
+
|
|
964
|
+
const data = collectFilterState()
|
|
965
|
+
const params = new URLSearchParams(window.location.hash.replace(/^#/, ''))
|
|
966
|
+
const serialized = encodeURIComponent(JSON.stringify(data))
|
|
967
|
+
params.set(URL_PARAM_KEY, serialized)
|
|
968
|
+
const nextHash = params.toString()
|
|
969
|
+
if (window.location.hash !== `#${nextHash}`) {
|
|
970
|
+
window.location.hash = nextHash
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
function hydrateFilterStateFromUrl() {
|
|
975
|
+
const params = new URLSearchParams(window.location.hash.replace(/^#/, ''))
|
|
976
|
+
const raw = params.get(URL_PARAM_KEY)
|
|
977
|
+
if (!raw) return
|
|
978
|
+
|
|
979
|
+
try {
|
|
980
|
+
const parsed = JSON.parse(decodeURIComponent(raw))
|
|
981
|
+
isHydratingFromUrl = true
|
|
982
|
+
if (typeof parsed.search === 'string') searchInput.value = parsed.search
|
|
983
|
+
if (typeof parsed.typeFilter === 'string') typeFilterInput.value = parsed.typeFilter
|
|
984
|
+
caseSensitiveInput.checked = Boolean(parsed.caseSensitive)
|
|
985
|
+
regexSearchInput.checked = Boolean(parsed.regex)
|
|
986
|
+
hasEnumOnlyInput.checked = Boolean(parsed.hasEnumOnly)
|
|
987
|
+
if (parsed.typeMatchMode === 'exact' || parsed.typeMatchMode === 'contains') {
|
|
988
|
+
typeMatchModeInput.value = parsed.typeMatchMode
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
if (Array.isArray(parsed.selectedFields)) {
|
|
992
|
+
const allowed = new Set(parsed.selectedFields)
|
|
993
|
+
SEARCH_FIELDS.forEach((field) => {
|
|
994
|
+
const input = document.getElementById(`field-${field.id}`)
|
|
995
|
+
if (!input) return
|
|
996
|
+
input.checked = allowed.has(field.id)
|
|
997
|
+
})
|
|
998
|
+
}
|
|
999
|
+
} catch (error) {
|
|
1000
|
+
// Ignore malformed hash state.
|
|
1001
|
+
} finally {
|
|
1002
|
+
isHydratingFromUrl = false
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
697
1006
|
async function handleFile(file) {
|
|
698
1007
|
const text = await file.text()
|
|
699
1008
|
const parsed = JSON.parse(text)
|
|
@@ -729,11 +1038,24 @@
|
|
|
729
1038
|
})
|
|
730
1039
|
|
|
731
1040
|
searchInput.addEventListener('input', renderResults)
|
|
1041
|
+
typeFilterInput.addEventListener('input', renderResults)
|
|
732
1042
|
caseSensitiveInput.addEventListener('change', renderResults)
|
|
733
1043
|
regexSearchInput.addEventListener('change', renderResults)
|
|
1044
|
+
hasEnumOnlyInput.addEventListener('change', renderResults)
|
|
1045
|
+
typeMatchModeInput.addEventListener('change', renderResults)
|
|
1046
|
+
|
|
1047
|
+
selectAllFieldsBtn.addEventListener('click', () =>
|
|
1048
|
+
setFieldSelection(new Set(SEARCH_FIELDS.map((field) => field.id))),
|
|
1049
|
+
)
|
|
1050
|
+
clearAllFieldsBtn.addEventListener('click', () => setFieldSelection(new Set()))
|
|
1051
|
+
schemasOnlyFieldsBtn.addEventListener('click', () => setFieldSelection(SCHEMA_FIELD_IDS))
|
|
1052
|
+
metadataOnlyFieldsBtn.addEventListener('click', () => setFieldSelection(METADATA_FIELD_IDS))
|
|
1053
|
+
resetFiltersBtn.addEventListener('click', resetFiltersToDefault)
|
|
734
1054
|
|
|
735
1055
|
renderFieldCheckboxes()
|
|
1056
|
+
hydrateFilterStateFromUrl()
|
|
736
1057
|
initializeFromBakedPayload()
|
|
1058
|
+
renderResults()
|
|
737
1059
|
</script>
|
|
738
1060
|
</body>
|
|
739
1061
|
</html>
|