Files
emailtemplate.it/public/assets/js/bridge/library-api.js
2025-12-04 22:33:05 +01:00

247 lines
11 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* /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 || '<div style="padding: 10px; color: #dc3545; background-color: #fce7f3; border: 1px solid #fbcfe8; text-align: center;">🛑 Fehler: Inhalt fehlte beim Laden.</div>',
                                 content: '', // Wichtig: Beim Drop keinen GrapesJS-Content setzen
},
attributes: { 'title': itemKindUpper },
media: item.preview_url ? `<img src="${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 = {}));