262 lines
12 KiB
JavaScript
262 lines
12 KiB
JavaScript
/* /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;
|
||
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 (!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,
|
||
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 || '<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 = {}));
|