|
| 1 | +// Simple JSON tree viewer for mdBook |
| 2 | +// Converts JSON code blocks into interactive collapsible trees |
| 3 | + |
| 4 | +(function() { |
| 5 | + function createJsonTree(obj, indent = 0) { |
| 6 | + const type = Array.isArray(obj) ? 'array' : typeof obj; |
| 7 | + |
| 8 | + if (type === 'object' && obj !== null) { |
| 9 | + const entries = Object.entries(obj); |
| 10 | + if (entries.length === 0) return '<span class="json-empty">{}</span>'; |
| 11 | + |
| 12 | + const items = entries.map(([key, value]) => { |
| 13 | + const valueHtml = createJsonTree(value, indent + 1); |
| 14 | + return `<div class="json-item" style="margin-left: ${indent * 16}px"> |
| 15 | + <span class="json-key">"${key}"</span>: ${valueHtml} |
| 16 | + </div>`; |
| 17 | + }).join(''); |
| 18 | + |
| 19 | + return `<span class="json-toggle" onclick="this.parentElement.classList.toggle('collapsed')">{...}</span> |
| 20 | + <div class="json-content">${items}</div>`; |
| 21 | + } else if (type === 'array') { |
| 22 | + if (obj.length === 0) return '<span class="json-empty">[]</span>'; |
| 23 | + |
| 24 | + const items = obj.map((value, i) => { |
| 25 | + const valueHtml = createJsonTree(value, indent + 1); |
| 26 | + return `<div class="json-item" style="margin-left: ${indent * 16}px"> |
| 27 | + <span class="json-index">${i}</span>: ${valueHtml} |
| 28 | + </div>`; |
| 29 | + }).join(''); |
| 30 | + |
| 31 | + return `<span class="json-toggle" onclick="this.parentElement.classList.toggle('collapsed')">[${obj.length}]</span> |
| 32 | + <div class="json-content">${items}</div>`; |
| 33 | + } else if (type === 'string') { |
| 34 | + return `<span class="json-string">"${obj}"</span>`; |
| 35 | + } else if (type === 'number') { |
| 36 | + return `<span class="json-number">${obj}</span>`; |
| 37 | + } else if (type === 'boolean') { |
| 38 | + return `<span class="json-boolean">${obj}</span>`; |
| 39 | + } else { |
| 40 | + return `<span class="json-null">null</span>`; |
| 41 | + } |
| 42 | + } |
| 43 | + |
| 44 | + function initJsonViewer() { |
| 45 | + // Find all JSON code blocks inside <details> elements |
| 46 | + document.querySelectorAll('details pre code.language-json').forEach(block => { |
| 47 | + try { |
| 48 | + const json = JSON.parse(block.textContent); |
| 49 | + const container = document.createElement('div'); |
| 50 | + container.className = 'json-tree'; |
| 51 | + container.innerHTML = createJsonTree(json); |
| 52 | + |
| 53 | + // Replace the pre element with the tree |
| 54 | + const pre = block.parentElement; |
| 55 | + pre.parentElement.insertBefore(container, pre); |
| 56 | + pre.style.display = 'none'; |
| 57 | + |
| 58 | + // Add toggle to show raw JSON |
| 59 | + const toggle = document.createElement('button'); |
| 60 | + toggle.className = 'json-raw-toggle'; |
| 61 | + toggle.textContent = 'Show raw JSON'; |
| 62 | + toggle.onclick = () => { |
| 63 | + if (pre.style.display === 'none') { |
| 64 | + pre.style.display = 'block'; |
| 65 | + container.style.display = 'none'; |
| 66 | + toggle.textContent = 'Show tree view'; |
| 67 | + } else { |
| 68 | + pre.style.display = 'none'; |
| 69 | + container.style.display = 'block'; |
| 70 | + toggle.textContent = 'Show raw JSON'; |
| 71 | + } |
| 72 | + }; |
| 73 | + container.parentElement.insertBefore(toggle, container); |
| 74 | + } catch (e) { |
| 75 | + // Not valid JSON, leave as-is |
| 76 | + } |
| 77 | + }); |
| 78 | + } |
| 79 | + |
| 80 | + // Run after DOM is ready |
| 81 | + if (document.readyState === 'loading') { |
| 82 | + document.addEventListener('DOMContentLoaded', initJsonViewer); |
| 83 | + } else { |
| 84 | + initJsonViewer(); |
| 85 | + } |
| 86 | +})(); |
0 commit comments