fix: JSON Editor unterstützt Arrays und Objekte via Textarea

- Komplexe JSON-Strukturen werden als formatierter Textbereich gerendert
- Benutzer kann JSON direkt mit korrekten Datentypen bearbeiten
- Arrays und verschachtelte Objekte bleiben als JSON erhalten

Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
Michael 2026-04-24 11:58:18 +02:00
parent 581b728c1b
commit ee1b8f7539

View file

@ -434,9 +434,7 @@
<h2 id="edit-title">Template bearbeiten</h2>
<button class="modal-close" onclick="closeEditModal()">&times;</button>
</div>
<div class="modal-body">
<textarea id="edit-content" style="width: 100%; min-height: 300px; background: var(--bg-input); color: var(--text-primary); border: 1px solid var(--border); border-radius: 4px; font-family: var(--mono); font-size: 13px; padding: 10px;" spellcheck="false"></textarea>
</div>
<div class="modal-body" id="edit-content-content" style="min-height: 300px; padding: 16px; overflow-y: auto;"></div>
<div class="modal-actions">
<button class="btn btn-primary" onclick="saveEditedContent()">Speichern</button>
<button class="btn" onclick="closeEditModal()">Abbrechen</button>
@ -514,89 +512,46 @@ $ python web/serve.py</div>
const title = path.split('/').pop();
document.getElementById('edit-title').textContent = `Template bearbeiten: ${title}`;
// Inhalt laden und je nach Dateityp bearbeiten
fetch(path).then(r => r.text()).then(content => {
if (path.endsWith('.json')) {
// JSON parsen und Formular für Key-Value-Paare erstellen
try {
const jsonData = JSON.parse(content);
renderJsonEditForm(jsonData);
} catch (e) {
// Falls JSON ungültig, trotzdem als Text bearbeiten
renderTextEditForm(content);
// Inhalt laden und editierbare Formulare abhängig vom Dateityp erstellen
fetch(path)
.then(r => r.text())
.then(content => {
const editContainer = document.getElementById('edit-content-content');
editContainer.innerHTML = '';
if (path.endsWith('.json')) {
try {
const jsonData = JSON.parse(content);
createJsonEditUI(editContainer, jsonData, path);
} catch (e) {
// Falls JSON ungültig, als Text bearbeiten
createTextEditUI(editContainer, content);
}
} else {
// Markdown als einfaches Textfeld
createTextEditUI(editContainer, content);
}
} else {
// Markdown als Text bearbeiten
renderTextEditForm(content);
}
document.getElementById('edit-modal').classList.add('active');
}).catch(e => {
showToast(`✗ Fehler beim Laden: ${e.message}`);
});
document.getElementById('edit-modal').classList.add('active');
})
.catch(e => showToast(`✗ Fehler beim Laden: ${e.message}`));
}
function renderTextEditForm(content) {
document.getElementById('edit-content').innerHTML = '<textarea id="edit-textarea" style="width: 100%; min-height: 300px; background: var(--bg-input); color: var(--text-primary); border: 1px solid var(--border); border-radius: 4px; font-family: var(--mono); font-size: 13px; padding: 10px;" spellcheck="false">' + escapeHtml(content) + '</textarea>';
function createTextEditUI(container, content) {
const textarea = document.createElement('textarea');
textarea.id = 'edit-textarea';
textarea.value = content;
textarea.style.cssText = 'width: 100%; min-height: 300px; background: var(--bg-input); color: var(--text-primary); border: 1px solid var(--border); border-radius: 4px; padding: 10px; font-family: var(--mono); font-size: 13px;';
textarea.spellcheck = false;
container.appendChild(textarea);
}
function renderJsonEditForm(jsonData) {
let html = '<div style="padding-bottom: 15px;"><button onclick="addJsonField()" class="btn" style="margin-bottom: 10px;">Neues Feld hinzufügen</button></div>';
html += '<div id="json-edit-fields">';
for (const key in jsonData) {
if (jsonData.hasOwnProperty(key)) {
html += createJsonField(key, jsonData[key]);
}
}
html += '</div>';
document.getElementById('edit-content').innerHTML = html;
}
function createJsonField(key, value) {
if (typeof value === 'object' && value !== null) {
let inner = '<table style="width: 100%; margin-bottom: 10px;">';
for (const innerKey in value) {
if (value.hasOwnProperty(innerKey)) {
inner += '<tr><td style="padding: 5px 10px; border-bottom: 1px solid var(--border-light);">' + innerKey + '</td><td style="padding: 5px 10px; border-bottom: 1px solid var(--border-light);"><input type="text" value="' + escapeHtml(String(value[innerKey])) + '" style="width: 100%; background: var(--bg-input); border: 1px solid var(--border); padding: 4px; color: var(--text-primary);"></td></tr>';
}
}
inner += '</table>';
return '<div style="background: var(--bg-input); border: 1px solid var(--border-light); border-radius: 4px; margin-bottom: 10px; padding: 10px;" class="json-field">' +
'<h4 style="margin-bottom: 8px;">' + key + '</h4>' + inner +
'<button onclick="removeJsonField(this)" style="background: var(--red); color: white; border: none; padding: 4px 8px; border-radius: 3px; cursor: pointer;">🗑️ Löschen</button>' +
'</div>';
} else {
return '<div style="background: var(--bg-input); border: 1px solid var(--border-light); border-radius: 4px; padding: 10px; margin-bottom: 10px;" class="json-field">' +
'<h4 style="margin-bottom: 5px;"><input type="text" value="' + key + '" style="width: 45%; background: var(--bg-input); border: 1px solid var(--border); padding: 4px; color: var(--text-primary); font-family: var(--mono);" placeholder="Key">' +
' <input type="text" value="' + escapeHtml(String(value)) + '" style="width: 45%; background: var(--bg-input); border: 1px solid var(--border); padding: 4px; color: var(--text-primary); font-family: var(--mono);" placeholder="Value"></h4>' +
'<button onclick="removeJsonField(this)" style="background: var(--red); color: white; border: none; padding: 4px 8px; border-radius: 3px; cursor: pointer;">🗑️ Löschen</button>' +
'</div>';
}
}
function addJsonField() {
const container = document.getElementById('json-edit-fields');
container.innerHTML += createJsonField('newField', '');
}
function removeJsonField(button) {
button.parentElement.remove();
}
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
function unescapeHtml(html) {
const txt = document.createElement("textarea");
txt.innerHTML = html;
return txt.value;
function createJsonEditUI(container, jsonData, editPathHint = '') {
const textarea = document.createElement('textarea');
textarea.value = JSON.stringify(jsonData, null, 2);
textarea.style.cssText = 'width: 100%; min-height: 300px; background: var(--bg-input); color: var(--text-primary); border: 1px solid var(--border); border-radius: 4px; padding: 10px; font-family: var(--mono); font-size: 13px;';
textarea.spellcheck = false;
container.appendChild(textarea);
}
async function saveEditedContent() {
@ -604,40 +559,34 @@ $ python web/serve.py</div>
try {
if (currentEditTemplate.endsWith('.json')) {
// JSON aus Formular extrahieren
const fields = document.querySelectorAll('.json-field');
const result = {};
const textarea = document.getElementById('edit-textarea');
const finalJsonString = textarea.value.trim();
for (const field of fields) {
const title = field.querySelector('h4 input[type="text"]:first-of-type');
const key = title ? title.value.trim() : '';
const value = field.querySelector('h4 input[type="text"]:last-of-type') ? field.querySelector('h4 input[type="text"]:last-of-type').value : '';
if (key) {
result[key] = value;
}
// Valid JSON prüfen
try {
JSON.parse(finalJsonString);
} catch (e) {
showToast('✗ Ungültiges JSON. Bitte korrigiere den Inhalt.');
return;
}
const jsonString = JSON.stringify(result, null, 2);
const response = await fetch(currentEditTemplate, {
method: 'PUT',
headers: {'Content-Type': 'text/plain'},
body: jsonString
body: finalJsonString
});
if (response.ok) {
showToast('✓ Änderungen gespeichert');
closeEditModal();
closeModal();
showModal(currentEditTemplate.split('/').pop(), jsonString);
viewTemplate(currentEditTemplate);
} else {
throw new Error(`HTTP ${response.status}`);
}
} else {
// Textinhalt direkt speichern
const textarea = document.getElementById('edit-textarea');
const content = textarea ? textarea.value : document.getElementById('edit-content').innerText;
const content = textarea.value;
const response = await fetch(currentEditTemplate, {
method: 'PUT',
@ -649,7 +598,7 @@ $ python web/serve.py</div>
showToast('✓ Änderungen gespeichert');
closeEditModal();
closeModal();
showModal(currentEditTemplate.split('/').pop(), content);
viewTemplate(currentEditTemplate);
} else {
throw new Error(`HTTP ${response.status}`);
}