/* /assets/js/bridge/library-api.js (FINAL & KORRIGIERT FÜR FLEXIBLE API-BASES) */ (function(B){ // 🛑 WICHTIG: Globalen Cache-Speicher initialisieren (wird von blocks-api.js gelesen) B.ApiItemCache = B.ApiItemCache || {}; if (!B || typeof grapesjs === 'undefined') return; const PluginName = 'bridge-library-api'; // NEU: Standard-API-Basis für Abwärtskompatibilität, falls nichts konfiguriert const API_BASE_FALLBACK = (B.API_BASE || '/api/editor'); // Konstanten const TARGET_CAT_ID = 'custom'; const PLACEHOLDER_ID = 'api-placeholder-loading'; const REFERENCE_COMPONENT_TYPE = 'library-reference'; if (B.LOG_CONFIG && B.LOG_CONFIG.PLUGINS) { B.LOG_CONFIG.PLUGINS[PluginName] = true; } const log = (type, message, color = '#6A5ACD', logType = 'info', force = false) => { if (typeof B.log === 'function') { B.log(PluginName, `[${type}] ${message}`, color, logType, force); } else { if (logType === 'error') { console.error(`%c[${PluginName} - ${type}] %c${message}`, 'color:red; font-weight:bold;', 'color:inherit;'); } } }; const logApiData = (data) => B.logData(PluginName, data); log('INIT', 'API-Schicht initialisiert.'); // --- HILFSFUNKTIONEN --- /** * Gibt die korrekte API-Basis-URL für einen Ressourcentyp (kind) zurück. * Nutzt die zentrale Map B.RESOURCE_API_BASES, die in category-config.js gefüllt wurde. */ const getApiBase = (resource) => { // Fallback auf die konfigurierte Standard-Basis, falls die Map noch nicht existiert oder der Eintrag fehlt. return (B.RESOURCE_API_BASES && B.RESOURCE_API_BASES[resource]) || API_BASE_FALLBACK; }; const buildApiUrl = (resource, action='list', params = {}) => { // KORREKTUR: Nutzt jetzt die dynamisch ermittelte API-Basis const apiBase = getApiBase(resource); const url = new URL(apiBase, window.location.origin); url.searchParams.set('resource', resource); url.searchParams.set('action', action); Object.entries(params).forEach(([key, value]) => { if (value !== null && value !== undefined) url.searchParams.set(key, value); }); return url.toString(); }; const shouldLoad = (resource) => { const mode = (B.EDITOR_MODE || 'TEMPLATES').toUpperCase(); // HINWEIS: Hier muss für neue Ressourcen (wie 'products') ggf. der mode angepasst werden, // falls sie nicht in TEMPLATES geladen werden sollen. switch (mode) { case 'TEMPLATES': const templateResources = ['templates', 'sections', 'blocks', 'snippets', 'products']; // Beispiel: products hinzugefügt return templateResources.includes(resource); case 'SECTIONS': const sectionResources = ['blocks', 'snippets']; return sectionResources.includes(resource); case 'BLOCKS': return resource === 'snippets'; default: log('MODE WARN', `Unbekannter Editor Modus '${mode}' festgestellt.`, 'orange', 'warn'); return resource === 'snippets'; } }; const fetchData = (resource, action='list', params = {}) => { // ... (Rest der fetchData-Funktion bleibt unverändert, nutzt aber die korrigierte buildApiUrl) const url = buildApiUrl(resource, action, params); const cacheKey = action === 'get' ? `${resource}-${params.id}` : null; // Cache-Check verwendet B.ApiItemCache if (cacheKey && B.ApiItemCache.hasOwnProperty(cacheKey)) { log('CACHE HIT', `Cache Hit für /${resource}-${cacheKey}.`, '#708090', 'info'); return Promise.resolve(B.ApiItemCache[cacheKey]); } return fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json' }, }) .then(response => { if (!response.ok) { log('API ERROR', `API-Aufruf fehlgeschlagen für /${resource}/${action}: ${response.status} (${response.statusText})`, 'red', 'error'); // 💡 KORREKTUR: Bei HTTP-Fehler immer ein leeres Array für LIST und leeres Objekt für GET zurückgeben. return action === 'get' ? {} : { items: [] }; } return response.json(); }) .then(data => { if (data.ok === false) { log('API ERROR', `API-Fehler für /${resource}: ${data.error || 'Unbekannt'}`, 'red', 'error'); // 💡 KORREKTUR: Bei API-Fehler ('ok: false') immer leeres Array/Objekt zurückgeben. return action === 'get' ? {} : { items: [] }; } const result = data.items || data.data || data.item; const finalResult = result ? (Array.isArray(result) ? result : (action === 'list' ? (result.items || []) : result)) : (action === 'list' ? [] : {}); const resultIsArray = Array.isArray(finalResult); const resultLength = resultIsArray ? finalResult.length : (Object.keys(finalResult).length > 0 ? 1 : 0); log('EXTRACT SUCCESS', `Extrahiert ${resultLength} Elemente (Typ: ${action}) für /${resource}.`); // Cache-Speicherung verwendet B.ApiItemCache if (cacheKey && resultLength > 0) { B.ApiItemCache[cacheKey] = finalResult; } // 💡 KORREKTUR: Bei LIST (action='list') geben wir immer ein Array zurück, sonst das Objekt return finalResult; }) .catch(error => { log('FETCH ERROR', `FEHLER beim Fetchen oder Parsen von /${resource}: ${error.message}`, 'red', 'error', true); return action === 'get' ? {} : []; }); }; // --- Exportierte Core-Funktionen (jetzt generisch) --- // NEU: Generische Fetch-Funktion für jeden Ressourcentyp ('kind') B.fetchResource = (kind) => { if (!shouldLoad(kind)) { log('BLOCKED', `Blockiert: ${kind} (Modus: ${B.EDITOR_MODE})`, '#708090', 'info'); return Promise.resolve([]); } return fetchData(kind).then(items => Array.isArray(items) ? items : []); }; // Die alten hardcodierten Funktionen verwenden jetzt die neue generische Funktion B.fetchTemplates = () => B.fetchResource('templates'); B.fetchSnippets = () => B.fetchResource('snippets'); B.fetchSections = () => B.fetchResource('sections'); B.fetchBlocks = () => B.fetchResource('blocks'); B.getApiItem = (kind, id) => fetchData(kind, 'get', { id: id }); B.clearApiCache = () => { B.ApiItemCache = {}; // Cache leeren log('CACHE CLEAR', `API-Cache geleert.`, 'orange', 'warn'); }; // 🚀 Zentrale Funktion zum Laden und Registrieren der Blöcke B.loadAndRegisterApiBlocks = (editor) => { const bm = editor.BlockManager; // NEU: Ressourcen-Kinds aus der Konfiguration sammeln const resourceKindsToLoad = Object.keys(B.RESOURCE_API_BASES || {}); if (resourceKindsToLoad.length === 0) { log('FEHLER', 'Keine Ressourcen-Kind-Konfiguration (B.RESOURCE_API_BASES) gefunden.', '#dc3545', 'error', true); bm.remove(PLACEHOLDER_ID); return; } // Map aller Fetch-Promises erstellen const fetchPromises = resourceKindsToLoad.map(kind => B.fetchResource(kind).then(items => items.map(i => ({ ...i, kind: kind }))) ); log('API START', `Starte Promise.all für API-Abruf der Blöcke/Sektionen (${resourceKindsToLoad.join(', ')})...`, '#1E90FF'); Promise.all(fetchPromises) .then(results => { const apiItems = results.flat().filter(item => item && item.id); log(`API SUCCESS`, `${apiItems.length} Elemente gefunden.`, '#9400D3'); logApiData(apiItems); if (apiItems.length === 0) { log('NO DATA', 'Keine API-Daten gefunden.', 'orange', 'warn', true); } else { apiItems.forEach(item => { const blockId = `lib-${item.kind}-${item.id}`; const label = item.name || item.label || 'Unbenannter Block'; const itemKindUpper = item.kind.toUpperCase(); // Hier wird der Block-Manager-Block registriert // ... (Der Rest der Logik bleibt unverändert) ... const blockDefinition = { label: label, category: TARGET_CAT_ID, // 💡 KORREKTUR: Immer die library-reference-Komponente verwenden, um die Referenz-Logik // (mit editable: false) aus blocks-api.js zu erzwingen. content: { type: REFERENCE_COMPONENT_TYPE, 'lib-kind': item.kind, 'lib-id': item.id, // NEU: startContent wird nur als reines HTML übergeben. // Die Logik in blocks-api.js (init/reloadComponentContent) kümmert sich um die Anzeige. startContent: item.html || item.content || '