/* /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) => {         if (Array.isArray(B.ALLOWED_SECTION_SLUGS) && B.ALLOWED_SECTION_SLUGS.length) {             return B.ALLOWED_SECTION_SLUGS.includes(resource);         }         const mode = (B.EDITOR_MODE || 'TEMPLATES').toUpperCase();         switch (mode) {             case 'TEMPLATES':                 return true;             default:                 return true;         }     };     const fetchSectionsConfig = async () => {         const rows = await fetchData('sections_config', 'list');         return Array.isArray(rows) ? rows : [];     };     const resolveAllowedSections = async () => {         const sections = await fetchSectionsConfig();         if (!sections.length) return [];         const modeSlug = String(B.EDITOR_MODE || '').toLowerCase();         const current = sections.find(s => String(s.slug || '').toLowerCase() === modeSlug);         if (!current) return sections;         return sections.filter(s => Number(s.position) > Number(current.position));     };     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; let 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}.`);                  // Bei GET-Antworten stellt der Server häufig html/content separat bereit. if (action === 'get' && finalResult && !resultIsArray) { if (data && typeof data.html !== 'undefined' && !('html' in finalResult)) { finalResult.html = data.html; } if (data && typeof data.content !== 'undefined' && !('content' in finalResult)) { finalResult.content = data.content; } } // 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 (B.USE_DYNAMIC_SECTIONS && ['templates','sections','blocks','snippets'].includes(String(kind || '').toLowerCase())) {             return Promise.resolve([]);         }         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; const loadDynamic = async () => { const sections = await resolveAllowedSections(); B.ALLOWED_SECTION_SLUGS = sections.map(s => String(s.slug || '').toLowerCase()); if (!sections.length) { bm.remove(PLACEHOLDER_ID); return []; } const promises = sections.map(section => fetchData('content', 'list', { section_id: section.id }) .then(items => (Array.isArray(items) ? items : []).map(i => ({ ...i, kind: String(section.slug || '').toLowerCase(), section_name: section.name || '', }))) ); const results = await Promise.all(promises); return results.flat(); }; const startLoad = (B.USE_DYNAMIC_SECTIONS ? loadDynamic() : Promise.resolve([])); log('API START', 'Starte API-Abruf für dynamische Sections...', '#1E90FF'); startLoad .then(apiItems => { const filtered = Array.isArray(apiItems) ? apiItems.filter(item => item && item.id) : [];   log(`API SUCCESS`, `${filtered.length} Elemente gefunden.`, '#9400D3'); logApiData(filtered);    if (filtered.length === 0) { log('NO DATA', 'Keine API-Daten gefunden.', 'orange', 'warn', true); bm.remove(PLACEHOLDER_ID); } else { filtered.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, attributes: { 'data-lib-kind': item.kind, 'data-lib-id': item.id, 'data-lib-ref': '1', },                                  // 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 || '
🛑 Fehler: Inhalt fehlte beim Laden.
',                                  content: '', // Wichtig: Beim Drop keinen GrapesJS-Content setzen }, attributes: { 'title': itemKindUpper }, media: item.preview_url ? `` : '', }; bm.add(blockId, blockDefinition); });   bm.remove(PLACEHOLDER_ID); log(`REGISTRATION`, `${apiItems.length} API-Blöcke registriert. Platzhalter entfernt.`, '#008000'); const reloadExistingComponents = () => { const allComponents = editor.DomComponents.getWrapper().find(`[data-gjs-type="${REFERENCE_COMPONENT_TYPE}"]`); allComponents.forEach(component => { if (component.get('lib-id') && component.components().length === 0 && typeof component.reloadComponentContent === 'function') { log(`RELOAD START`, `Lade ${component.get('lib-kind')}/${component.get('lib-id')} nach Cache-Füllung (Sicherheitsnetz).`, '#FF4500'); component.reloadComponentContent({ forced: true, reason: 'EXISTING_CONTENT_RELOAD' });  } }); }; setTimeout(reloadExistingComponents, 100); } }) .catch(error => { // Hier wird der Fehler von fetchData oder map abgefangen log('FETCH ERROR', `FEHLER beim Laden der API-Blöcke: ${error.message}`, '#dc3545', 'error', true); bm.remove(PLACEHOLDER_ID); }); }; })(window.BridgeParts || (window.BridgeParts = {}));