/** * 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)}${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 `