/* createJsonTree({ container: domElement, data: jsonData, onChange: (data) => { }, // optional: callback on change onSave: (data) => { } // optional: if set, a save button will be added }); */function createJsonTree({ container, data, onChange = () => {}, onSave = null }) { container.innerHTML = ""; container.classList.add("json-tree-root"); const history = []; const redoStack = []; let lastSnapshot = clone(data); function clone(obj) { return JSON.parse(JSON.stringify(obj)); } function diff(oldObj, newObj) { const changes = {}; function walk(o, n, path = "") { const keys = new Set([ ...Object.keys(o || {}), ...Object.keys(n || {}) ]); for (const key of keys) { const oldVal = o?.[key]; const newVal = n?.[key]; const currentPath = path ? `${path}.${key}` : key; if ( typeof oldVal === "object" && oldVal !== null && typeof newVal === "object" && newVal !== null && !Array.isArray(oldVal) ) { walk(oldVal, newVal, currentPath); continue; } if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) { changes[currentPath] = newVal; } } } walk(oldObj, newObj); return changes; } function pushHistory() { history.push(clone(data)); redoStack.length = 0; } function undo() { if (!history.length) return; redoStack.push(clone(data)); data = history.pop(); render(); } function redo() { if (!redoStack.length) return; history.push(clone(data)); data = redoStack.pop(); render(); } function save() { if (onSave) onSave(data); } function autoResize(input) { input.style.width = "10px"; input.style.width = input.scrollWidth + "px"; } function remove(key, parent) { if (key === null || parent == null) return; if (!confirm("Wirklich löschen?")) return; pushHistory(); if (Array.isArray(parent)) { parent.splice(key, 1); } else { delete parent[key]; } onChange(data); render(); } function render() { container.innerHTML = ""; const controls = document.createElement("div"); controls.className = "json-controls"; const undoBtn = document.createElement("button"); undoBtn.className = "monolyth"; undoBtn.textContent = "Undo"; undoBtn.onclick = undo; const redoBtn = document.createElement("button"); redoBtn.className = "monolyth"; redoBtn.textContent = "Redo"; redoBtn.onclick = redo; controls.appendChild(undoBtn); controls.appendChild(redoBtn); if (onSave) { const saveBtn = document.createElement("button"); saveBtn.className = "monolyth"; saveBtn.textContent = "Save"; saveBtn.onclick = save; controls.appendChild(saveBtn); } container.appendChild(controls); container.appendChild(renderNode(data, null, null, "root", 0)); } function renderNode(value, key, parent, path, level) { const wrapper = document.createElement("div"); wrapper.className = "json-line"; wrapper.style.marginLeft = `${level * 18}px`; const keySpan = document.createElement("span"); keySpan.className = "json-key"; if (key !== null) { keySpan.textContent = `"${key}": `; } const removeBtn = document.createElement("span"); removeBtn.className = "json-remove"; removeBtn.textContent = " [x]"; removeBtn.onclick = (evt) => { evt.stopPropagation(); remove(key, parent); }; // OBJECT / ARRAY if (typeof value === "object" && value !== null) { const isArray = Array.isArray(value); const header = document.createElement("div"); header.className = "json-header"; const toggle = document.createElement("span"); toggle.className = "json-toggle"; toggle.textContent = isArray ? "[ ]" : "{ }"; const keyLabel = document.createElement("span"); keyLabel.className = "json-key"; if (key !== null) keyLabel.textContent = `"${key}": `; const addBtn = document.createElement("span"); addBtn.className = "json-add"; addBtn.textContent = " [+]"; const children = document.createElement("div"); children.className = "json-children"; keyLabel.onclick = toggle.onclick = () => { children.classList.toggle("collapsed"); }; addBtn.onclick = () => { let newKey = ""; let newValue; feedbox({ title: `Key hinzufügen`, message: `