prompt_template/web/js/editor.js

287 lines
11 KiB
JavaScript
Raw Normal View History

/**
* JSON-Editor für die Template-Bearbeitung.
*
* Generiert ein Formular aus JSON-Daten und extrahiert
* die Werte zurück in ein JSON-Objekt.
*/
let editContainerRef = null;
let currentIndent = 2;
/**
* Öffne Edit-Modal für eine Template-Datei
* @param {string} path - Pfad zur Template-Datei
*/
function editModalContent(path) {
window.currentEditTemplate = path;
const title = path.split('/').pop();
document.getElementById('edit-title').textContent = `Template bearbeiten: ${title}`;
window._wasViewModalOpen = document.getElementById('modal').classList.contains('active');
// 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);
// Original-Indent erkennen
const indentMatch = content.match(/^\s{2,8}/m);
currentIndent = indentMatch ? indentMatch[0].length : 2;
createJsonEditUI(editContainer, jsonData, path);
} catch (e) {
// Falls JSON ungültig, als Text bearbeiten
createTextEditUI(editContainer, content);
}
} else {
// Markdown als einfaches Textfeld
createTextEditUI(editContainer, content);
}
document.getElementById('edit-modal').classList.add('active');
})
.catch(e => showToast(`✗ Fehler beim Laden: ${e.message}`));
}
/**
* Erstelle ein Textarea für die Bearbeitung
* @param {HTMLElement} container - Ziel-Container
* @param {string} content - Dateiinhalt
*/
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);
}
/**
* Erstelle ein Formular aus JSON-Daten
* @param {HTMLElement} container - Ziel-Container
* @param {Object} jsonData - Zu bearbeitende JSON-Daten
* @param {string} editPathHint - Pfad-Hinweis
*/
function createJsonEditUI(container, jsonData, editPathHint = '') {
editContainerRef = container;
container.innerHTML = '';
const formDiv = document.createElement('div');
formDiv.style.cssText = 'display: flex; flex-direction: column; gap: 16px; padding: 8px; background: var(--bg-card); border-radius: 4px; margin: 0; min-height: 300px; overflow-y: auto;';
// Rekursive Funktion zum Erstellen von Eingabefeldern für alle Properties
function buildJsonForm(data, prefix = '', level = 0, targetElement = null) {
if (typeof data !== 'object' || data === null) return;
const container = targetElement || document.getElementById('edit-content-content');
if (!container) return;
Object.keys(data).forEach(key => {
const fullKey = prefix ? `${prefix}.${key}` : key;
const value = data[key];
const isObject = typeof value === 'object' && value !== null && !Array.isArray(value);
const isArray = Array.isArray(value);
// Für Objekte: Rekursiv alle inneren Properties anzeigen
if (isObject) {
const fieldContainer = document.createElement('div');
fieldContainer.style.cssText = 'background: #1a1a1a; padding: 12px; border-radius: 4px; border-left: 3px solid #4CAF50;';
const label = document.createElement('label');
label.htmlFor = `edit-${fullKey.replace(/[.\s]/g, '-')}`;
label.textContent = fullKey + ' (Objekt)';
label.style.cssText = 'font-weight: 600; color: #4CAF50; margin-bottom: 8px; display: block; font-size: 14px;';
fieldContainer.appendChild(label);
// Rekursiv innere Properties in den Container einfügen
const innerContainer = document.createElement('div');
innerContainer.style.cssText = 'padding-left: 12px; margin-top: 4px;';
buildJsonForm(value, fullKey, level + 1, innerContainer);
fieldContainer.appendChild(innerContainer);
container.appendChild(fieldContainer);
return;
}
// Für Arrays: Rekursiv jedes Element als eigenes Feld anzeigen
if (isArray) {
const arrayContainer = document.createElement('div');
arrayContainer.style.cssText = 'background: #1a1a1a; padding: 12px; border-radius: 4px; border-left: 3px solid #2196F3;';
const arrayLabel = document.createElement('label');
arrayLabel.htmlFor = `edit-${fullKey.replace(/[.\s]/g, '-')}`;
arrayLabel.textContent = fullKey + ' (Array) - ' + value.length + ' Elemente';
arrayLabel.style.cssText = 'font-weight: 600; color: #2196F3; margin-bottom: 8px; display: block; font-size: 14px;';
arrayContainer.appendChild(arrayLabel);
// Für jedes Array-Element ein separates Eingabefeld
const arrayItemsContainer = document.createElement('div');
arrayItemsContainer.style.cssText = 'margin-top: 4px; padding-left: 12px;';
value.forEach((item, index) => {
const itemKey = `${fullKey}[${index}]`;
const itemContainer = document.createElement('div');
itemContainer.style.cssText = 'background: #2a2a2a; padding: 8px; margin: 4px 0; border-radius: 3px; border-left: 2px solid #FF9800;';
const itemLabel = document.createElement('label');
itemLabel.htmlFor = `edit-${itemKey.replace(/[.\s]/g, '-')}`;
itemLabel.textContent = `Element [${index}]`;
itemLabel.style.cssText = 'font-weight: 500; color: #FF9800; margin-bottom: 4px; display: block; font-size: 13px;';
itemContainer.appendChild(itemLabel);
// Prüfen, ob das Array-Element selbst ein Objekt ist
if (typeof item === 'object' && item !== null) {
const innerObjContainer = document.createElement('div');
innerObjContainer.style.cssText = 'padding-left: 12px; margin-top: 4px;';
buildJsonForm(item, itemKey, level + 1, innerObjContainer);
itemContainer.appendChild(innerObjContainer);
} else {
const itemFieldContainer = document.createElement('div');
itemFieldContainer.style.cssText = 'background: var(--bg-input); padding: 8px; border-radius: 3px; margin-bottom: 4px;';
const input = document.createElement('input');
const type = typeof item === 'boolean' ? 'checkbox' : typeof item === 'number' ? 'number' : 'text';
input.type = type;
input.value = item !== null && item !== undefined ? item : '';
input.dataset.key = itemKey;
input.dataset.type = typeof item;
input.dataset.arrayIndex = index;
if (type === 'checkbox') input.checked = item;
else input.value = String(item !== null && item !== undefined ? item : '');
input.style.cssText = 'width: 100%; padding: 8px; background: #222222; color: #ffffff; border: 1px solid #555; border-radius: 3px; font-family: var(--mono); font-size: 13px;';
itemFieldContainer.appendChild(input);
itemContainer.appendChild(itemFieldContainer);
}
arrayItemsContainer.appendChild(itemContainer);
});
arrayContainer.appendChild(arrayItemsContainer);
container.appendChild(arrayContainer);
return;
}
// Für primitive Werte: Standard-Eingabefeld erstellen
const fieldContainer = document.createElement('div');
fieldContainer.style.cssText = 'background: var(--bg-input); padding: 8px; border-radius: 4px; border: 1px solid transparent;';
const label = document.createElement('label');
label.htmlFor = `edit-${fullKey.replace(/[.\s]/g, '-')}`;
label.textContent = fullKey;
label.style.cssText = 'font-weight: 600; color: var(--text-primary); display: block; margin-bottom: 4px; font-size: 14px; margin-top: 0;';
fieldContainer.appendChild(label);
const type = typeof value === 'boolean' ? 'checkbox' : typeof value === 'number' ? 'number' : 'text';
const input = document.createElement('input');
input.type = type;
input.value = value !== null && value !== undefined ? value : '';
input.dataset.key = fullKey;
input.dataset.type = typeof value;
if (type === 'checkbox') input.checked = value;
else input.value = String(value !== null && value !== undefined ? value : '');
input.style.cssText = 'width: 100%; padding: 8px; background: #222222; color: #ffffff; border: 1px solid #555; border-radius: 3px; font-family: var(--mono); font-size: 13px;';
fieldContainer.appendChild(input);
container.appendChild(fieldContainer);
});
}
buildJsonForm(jsonData, '', 0, formDiv);
container.appendChild(formDiv);
}
/**
* Extrahiere den Wert aus einem Input-Element
* @param {HTMLElement} input - Input-Element
* @returns {*} Extrahierter Wert
*/
function extractInputValue(input) {
if (input.type === 'checkbox') {
return input.checked;
}
const value = input.value;
try {
// Nur wenn der Input-Typ "text" ist und Wert JSON-ähnlich formatiert
if (input.type === 'text' && value.length > 0) {
if ((value.startsWith('{') && value.endsWith('}')) ||
(value.startsWith('[') && value.endsWith(']'))) {
try {
return JSON.parse(value);
} catch (parseErr) {
// Kein gültiges JSON, behalte String-Wert
}
}
}
if (input.dataset.type === 'number') {
return Number(value);
}
return value;
} catch (e) {
return value;
}
}
/**
* Extrahiere JSON aus dem Formular-Container
* @param {HTMLElement} formDiv - Formular-Container
* @returns {Object} Extrahiertes JSON-Objekt
*/
function extractJsonFromForm(formDiv) {
// Map: full key -> { value, type }
const inputEntries = [];
const inputs = formDiv.querySelectorAll('[data-key]');
inputs.forEach(input => {
const key = input.dataset.key || '';
inputEntries.push({ key, value: extractInputValue(input) });
});
// Sort by key to ensure stable parent-first construction
inputEntries.sort((a,b) => a.key.localeCompare(b.key));
const result = {};
inputEntries.forEach(({ key, value }) => {
const parts = key.split('.');
let cur = result;
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
const isLast = i === parts.length - 1;
const m = part.match(/^([^\[\]]+)\[(\d+)\]$/);
if (m && !isLast) {
const base = m[1];
const idx = Number(m[2]);
if (!cur[base]) cur[base] = [];
if (!cur[base][idx]) cur[base][idx] = {};
cur = cur[base][idx];
} else if (m && isLast) {
// Array leaf
const base = m[1];
const idx = Number(m[2]);
if (!cur[base]) cur[base] = [];
cur[base][idx] = value;
} else if (isLast) {
cur[part] = value;
} else {
if (!cur[part]) cur[part] = {};
cur = cur[part];
}
}
});
return result;
}
// Export for api.js (global scope, loaded before api.js)
// editModalContent, createTextEditUI, createJsonEditUI, extractInputValue, extractJsonFromForm
// sind als globale Funktionen verfügbar