commit
This commit is contained in:
246
public/assets/js/bridge/library-api.js
Normal file
246
public/assets/js/bridge/library-api.js
Normal file
@@ -0,0 +1,246 @@
|
||||
/* /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 = {}));
|
||||
Reference in New Issue
Block a user