/** * JSON View - JSON tree viewer with collapsible nodes */ import { BaseView, escapeHtml } from "./base_view.js"; class JsonView extends BaseView { static id = "json"; static displayName = "JSON"; static priority = 90; static detect(content) { const trimmed = content.trim(); if (trimmed.startsWith("{") || trimmed.startsWith("[")) { try { JSON.parse(trimmed); return 100; } catch {} } return 0; } static renderTree(data, depth = 0) { const indent = " ".repeat(depth); const childIndent = " ".repeat(depth + 1); if (data === null) return `null`; if (typeof data === "boolean") return `${data}`; if (typeof data === "number") return `${data}`; if (typeof data === "string") return `"${escapeHtml(data)}"`; if (Array.isArray(data)) { if (data.length === 0) return `[]`; const id = `json-${Math.random().toString(36).slice(2, 9)}`; const items = data.map((item, i) => { const comma = i < data.length - 1 ? "," : ""; return `${childIndent}${this.renderTree(item, depth + 1)}${comma}`; }).join("\n"); return `▼[${data.length} items ${items} ${indent}]]`; } if (typeof data === "object") { const keys = Object.keys(data); if (keys.length === 0) return `{}`; const id = `json-${Math.random().toString(36).slice(2, 9)}`; const items = keys.map((key, i) => { const comma = i < keys.length - 1 ? "," : ""; return `${childIndent}"${escapeHtml(key)}": ${this.renderTree(data[key], depth + 1)}${comma}`; }).join("\n"); return `▼{${keys.length} keys ${items} ${indent}}}`; } return escapeHtml(String(data)); } static render(content, theme) { try { const parsed = JSON.parse(content); return `
${this.renderTree(parsed)}
`; } catch { return `
${escapeHtml(content)}
`; } } static getStyles(theme) { return ` .json-tree pre { margin: 0; white-space: pre-wrap; font-size: 13px; line-height: 1.4; } .json-key { color: #9cdcfe; } .json-string { color: #ce9178; } .json-number { color: #b5cea8; } .json-boolean { color: #569cd6; } .json-null { color: #569cd6; } .json-bracket { color: ${theme.fg}; } .json-node { display: inline; } .json-toggle { cursor: pointer; user-select: none; } .json-toggle:hover { opacity: 0.7; } .json-arrow { display: inline-block; width: 1em; font-size: 10px; transition: transform 0.15s; color: #888; } .json-collapsed-preview { color: #6a9955; font-size: 11px; font-style: italic; display: none; } .json-collapsed-bracket { display: none; } .json-collapsible.collapsed { display: none; } .json-collapsible.collapsed + .json-collapsed-bracket { display: inline; } .json-node:has(> .json-collapsible.collapsed) .json-collapsed-preview { display: inline; margin-left: 4px; } .json-node:has(> .json-collapsible.collapsed) .json-arrow { transform: rotate(-90deg); } `; } static getScripts() { return `