@zjy4fun/json-open 0.1.9 → 0.2.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/bin/json.js +203 -0
- package/package.json +1 -1
package/bin/json.js
CHANGED
|
@@ -224,12 +224,71 @@ function toHtml(jsonObj, deepParsedObj) {
|
|
|
224
224
|
.null { color: #cbd5e1; }
|
|
225
225
|
.symbol { color: #c4b5fd; }
|
|
226
226
|
.meta { color: #64748b; }
|
|
227
|
+
/* 搜索框 */
|
|
228
|
+
.search-wrap {
|
|
229
|
+
display: flex;
|
|
230
|
+
align-items: center;
|
|
231
|
+
gap: 6px;
|
|
232
|
+
}
|
|
233
|
+
.search-wrap input {
|
|
234
|
+
border: 1px solid #475569;
|
|
235
|
+
background: #1e293b;
|
|
236
|
+
color: #e2e8f0;
|
|
237
|
+
border-radius: 8px;
|
|
238
|
+
padding: 7px 12px;
|
|
239
|
+
font-size: 13px;
|
|
240
|
+
font-family: inherit;
|
|
241
|
+
width: 200px;
|
|
242
|
+
outline: none;
|
|
243
|
+
transition: border-color 0.2s;
|
|
244
|
+
}
|
|
245
|
+
.search-wrap input:focus {
|
|
246
|
+
border-color: #58a6ff;
|
|
247
|
+
}
|
|
248
|
+
.search-wrap input::placeholder {
|
|
249
|
+
color: #64748b;
|
|
250
|
+
}
|
|
251
|
+
.search-count {
|
|
252
|
+
font-size: 12px;
|
|
253
|
+
color: #64748b;
|
|
254
|
+
min-width: 60px;
|
|
255
|
+
}
|
|
256
|
+
.search-nav button {
|
|
257
|
+
padding: 4px 8px;
|
|
258
|
+
font-size: 12px;
|
|
259
|
+
border-radius: 6px;
|
|
260
|
+
}
|
|
261
|
+
/* 搜索高亮 */
|
|
262
|
+
mark.highlight {
|
|
263
|
+
background: #f0883e;
|
|
264
|
+
color: #0d1117;
|
|
265
|
+
border-radius: 2px;
|
|
266
|
+
padding: 0 1px;
|
|
267
|
+
}
|
|
268
|
+
mark.highlight.current {
|
|
269
|
+
background: #58a6ff;
|
|
270
|
+
color: #fff;
|
|
271
|
+
box-shadow: 0 0 0 2px rgba(88,166,255,0.4);
|
|
272
|
+
}
|
|
273
|
+
/* 搜索时隐藏不匹配的行 */
|
|
274
|
+
.search-active .line.hidden-by-search,
|
|
275
|
+
.search-active li.hidden-by-search {
|
|
276
|
+
display: none;
|
|
277
|
+
}
|
|
227
278
|
</style>
|
|
228
279
|
</head>
|
|
229
280
|
<body>
|
|
230
281
|
<div class=\"toolbar\">
|
|
231
282
|
<button id=\"expand-all\">Expand all</button>
|
|
232
283
|
<button id=\"collapse-all\">Collapse all</button>
|
|
284
|
+
<div class=\"search-wrap\">
|
|
285
|
+
<input type=\"text\" id=\"search-input\" placeholder=\"Search...\" autocomplete=\"off\" />
|
|
286
|
+
<span class=\"search-count\" id=\"search-count\"></span>
|
|
287
|
+
<span class=\"search-nav\">
|
|
288
|
+
<button id=\"search-prev\" title=\"Previous (Shift+Enter)\">▲</button>
|
|
289
|
+
<button id=\"search-next\" title=\"Next (Enter)\">▼</button>
|
|
290
|
+
</span>
|
|
291
|
+
</div>
|
|
233
292
|
<div class=\"toggle-wrap${hasDiff ? '' : ' hidden'}\" title=\"Parse embedded JSON strings inside values\">
|
|
234
293
|
<span>Parse JSON strings</span>
|
|
235
294
|
<label class=\"toggle\">
|
|
@@ -257,8 +316,152 @@ function toHtml(jsonObj, deepParsedObj) {
|
|
|
257
316
|
rawView.style.display = ''
|
|
258
317
|
parsedView.style.display = 'none'
|
|
259
318
|
}
|
|
319
|
+
// 切换视图后重新搜索
|
|
320
|
+
if (searchInput.value.trim()) doSearch()
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ===== 搜索功能 =====
|
|
325
|
+
const searchInput = document.getElementById('search-input')
|
|
326
|
+
const searchCount = document.getElementById('search-count')
|
|
327
|
+
const searchPrev = document.getElementById('search-prev')
|
|
328
|
+
const searchNext = document.getElementById('search-next')
|
|
329
|
+
let highlights = []
|
|
330
|
+
let currentIdx = -1
|
|
331
|
+
|
|
332
|
+
function getActiveView() {
|
|
333
|
+
return parsedView.style.display === 'none' ? rawView : parsedView
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function clearSearch() {
|
|
337
|
+
// 移除所有高亮
|
|
338
|
+
document.querySelectorAll('mark.highlight').forEach(mark => {
|
|
339
|
+
const parent = mark.parentNode
|
|
340
|
+
parent.replaceChild(document.createTextNode(mark.textContent), mark)
|
|
341
|
+
parent.normalize()
|
|
260
342
|
})
|
|
343
|
+
highlights = []
|
|
344
|
+
currentIdx = -1
|
|
345
|
+
searchCount.textContent = ''
|
|
346
|
+
document.body.classList.remove('search-active')
|
|
261
347
|
}
|
|
348
|
+
|
|
349
|
+
function doSearch() {
|
|
350
|
+
clearSearch()
|
|
351
|
+
const query = searchInput.value.trim()
|
|
352
|
+
if (!query) return
|
|
353
|
+
|
|
354
|
+
const view = getActiveView()
|
|
355
|
+
const regex = new RegExp(query.replace(/[.*+?^\${}()|[\\]\\\\]/g, '\\\\$&'), 'gi')
|
|
356
|
+
|
|
357
|
+
// 遍历所有文本节点进行高亮
|
|
358
|
+
const walker = document.createTreeWalker(view, NodeFilter.SHOW_TEXT, null)
|
|
359
|
+
const textNodes = []
|
|
360
|
+
while (walker.nextNode()) textNodes.push(walker.currentNode)
|
|
361
|
+
|
|
362
|
+
textNodes.forEach(node => {
|
|
363
|
+
const text = node.textContent
|
|
364
|
+
if (!regex.test(text)) return
|
|
365
|
+
regex.lastIndex = 0
|
|
366
|
+
|
|
367
|
+
const frag = document.createDocumentFragment()
|
|
368
|
+
let lastIdx = 0
|
|
369
|
+
let match
|
|
370
|
+
while ((match = regex.exec(text)) !== null) {
|
|
371
|
+
if (match.index > lastIdx) {
|
|
372
|
+
frag.appendChild(document.createTextNode(text.slice(lastIdx, match.index)))
|
|
373
|
+
}
|
|
374
|
+
const mark = document.createElement('mark')
|
|
375
|
+
mark.className = 'highlight'
|
|
376
|
+
mark.textContent = match[0]
|
|
377
|
+
frag.appendChild(mark)
|
|
378
|
+
lastIdx = regex.lastIndex
|
|
379
|
+
}
|
|
380
|
+
if (lastIdx < text.length) {
|
|
381
|
+
frag.appendChild(document.createTextNode(text.slice(lastIdx)))
|
|
382
|
+
}
|
|
383
|
+
node.parentNode.replaceChild(frag, node)
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
highlights = Array.from(view.querySelectorAll('mark.highlight'))
|
|
387
|
+
if (highlights.length > 0) {
|
|
388
|
+
// 展开所有包含匹配的 <details>
|
|
389
|
+
highlights.forEach(h => {
|
|
390
|
+
let el = h.parentElement
|
|
391
|
+
while (el && el !== view) {
|
|
392
|
+
if (el.tagName === 'DETAILS') el.open = true
|
|
393
|
+
el = el.parentElement
|
|
394
|
+
}
|
|
395
|
+
})
|
|
396
|
+
currentIdx = 0
|
|
397
|
+
scrollToCurrent()
|
|
398
|
+
}
|
|
399
|
+
updateCount()
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function updateCount() {
|
|
403
|
+
if (highlights.length === 0 && searchInput.value.trim()) {
|
|
404
|
+
searchCount.textContent = 'No match'
|
|
405
|
+
} else if (highlights.length > 0) {
|
|
406
|
+
searchCount.textContent = (currentIdx + 1) + ' / ' + highlights.length
|
|
407
|
+
} else {
|
|
408
|
+
searchCount.textContent = ''
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function scrollToCurrent() {
|
|
413
|
+
highlights.forEach((h, i) => {
|
|
414
|
+
h.classList.toggle('current', i === currentIdx)
|
|
415
|
+
})
|
|
416
|
+
if (highlights[currentIdx]) {
|
|
417
|
+
highlights[currentIdx].scrollIntoView({ behavior: 'smooth', block: 'center' })
|
|
418
|
+
}
|
|
419
|
+
updateCount()
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function goNext() {
|
|
423
|
+
if (highlights.length === 0) return
|
|
424
|
+
currentIdx = (currentIdx + 1) % highlights.length
|
|
425
|
+
scrollToCurrent()
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function goPrev() {
|
|
429
|
+
if (highlights.length === 0) return
|
|
430
|
+
currentIdx = (currentIdx - 1 + highlights.length) % highlights.length
|
|
431
|
+
scrollToCurrent()
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// 输入时实时搜索(防抖 200ms)
|
|
435
|
+
let debounceTimer
|
|
436
|
+
searchInput.addEventListener('input', () => {
|
|
437
|
+
clearTimeout(debounceTimer)
|
|
438
|
+
debounceTimer = setTimeout(doSearch, 200)
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
// Enter = 下一个,Shift+Enter = 上一个
|
|
442
|
+
searchInput.addEventListener('keydown', (e) => {
|
|
443
|
+
if (e.key === 'Enter') {
|
|
444
|
+
e.preventDefault()
|
|
445
|
+
e.shiftKey ? goPrev() : goNext()
|
|
446
|
+
}
|
|
447
|
+
if (e.key === 'Escape') {
|
|
448
|
+
searchInput.value = ''
|
|
449
|
+
clearSearch()
|
|
450
|
+
searchInput.blur()
|
|
451
|
+
}
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
searchNext.addEventListener('click', goNext)
|
|
455
|
+
searchPrev.addEventListener('click', goPrev)
|
|
456
|
+
|
|
457
|
+
// Ctrl+F / Cmd+F 聚焦搜索框
|
|
458
|
+
document.addEventListener('keydown', (e) => {
|
|
459
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'f') {
|
|
460
|
+
e.preventDefault()
|
|
461
|
+
searchInput.focus()
|
|
462
|
+
searchInput.select()
|
|
463
|
+
}
|
|
464
|
+
})
|
|
262
465
|
</script>
|
|
263
466
|
</body>
|
|
264
467
|
</html>`
|