/* /assets/js/bridge/blocks-api.js (UI-KERN UND KOMPONENTEN-SCHICHT) */ (function (B) { const PluginName = 'bridge-blocks-api'; if (!B || typeof grapesjs === 'undefined') { console.warn(`%c[${PluginName}] %cBridgeParts (B) oder GrapesJS fehlt. Exit.`, 'color:orange; font-weight:bold;', 'color:inherit;'); return; } B.LOG_CONFIG = B.LOG_CONFIG || { PLUGINS: {} }; B.LOG_CONFIG.PLUGINS[PluginName] = true; const log = (message, color = '#1E90FF', type = 'info', force = false) => B.log(PluginName, message, color, type, force); const qs = new URLSearchParams(location.search); const requestedMode = (qs.get('mode') || 'templates').toLowerCase(); B.EDITOR_MODE = (B.EDITOR_MODE || requestedMode.toUpperCase()); const EDITOR_MODE = (B.EDITOR_MODE || 'TEMPLATES').toLowerCase(); log(`START: SKRIPT-AUSFÜHRUNG GESTARTET. Editor Modus: ${EDITOR_MODE}.`, '#DC143C'); const TARGET_CAT_ID = 'custom'; const PLACEHOLDER_ID = 'api-placeholder-loading'; const REFERENCE_COMPONENT_TYPE = 'library-reference'; // --- NEUE KONSTANTEN FÜR SPEICHERN-LOGIK --- // Annahme: ID der aktuellen Seite/Template ist global in B verfügbar const CURRENT_ENTITY_ID = Number(B.CURRENT_ENTITY_ID || qs.get('id') || 0); // Annahme: Basis-URL der API ist in B verfügbar const API_KERNEL_URL = B.API_KERNEL_URL || B.API_BASE || '/api.php'; // ------------------------------------------- // -------------------------------------------------------- // (1) Kern-Logik: Platzhalter und Kategorien registrieren (SYNCHRON) // -------------------------------------------------------- const preRegisterCategoriesAndPlaceholders = (editor) => { const bm = editor.BlockManager; bm.add(PLACEHOLDER_ID, { label: 'Lade Custom-Blöcke...', category: TARGET_CAT_ID, content: '
⚙️ Custom-Blöcke werden geladen...
', attributes: { class: 'gjs-block__api-placeholder' }, }); const cat = bm.getCategories().get(TARGET_CAT_ID); if (!cat) { bm.addCategory(TARGET_CAT_ID, { label: 'Custom', open: true, order: 1 }); } log('Platzhalter und Kategorie registriert.', '#008000'); }; // -------------------------------------------------------- // (2) Komponenten-Logik (ASYNCHRONER WORKAROUND & FIX) // -------------------------------------------------------- const registerReferenceComponent = (editor) => { const domc = editor.DomComponents; const defaultType = domc.getType('default'); if (!defaultType) return; log(`Starte Registrierung des Komponententyps '${REFERENCE_COMPONENT_TYPE}'.`, '#1E90FF'); setTimeout(() => { domc.addType(REFERENCE_COMPONENT_TYPE, { model: defaultType.model.extend({ getCachedApiItem(kind, id) { const key = `${kind}-${id}`; const item = B.ApiItemCache?.[key];  return item; }, init() { const id = this.get('lib-id'); const kind = this.get('lib-kind'); const startContent = this.get('startContent'); log(`INIT LÄUFT. lib-kind: ${kind}, lib-id: ${id}. (Bestätigung des Element-Drops/Load)`, '#8A2BE2'); if (startContent) { // 💡 NEUER FIX: Beim Drop nur die 'content'-Eigenschaft setzen, NICHT als Unterkomponenten parsen this.set('content', startContent);   this.unset('startContent'); log(`INHALT erfolgreich als REINES HTML aus 'startContent' gesetzt: ${kind}/${id}`, '#008000'); } this.on('change:lib-kind change:lib-id', this.reloadComponentContent); if (!startContent && kind && id) { this.reloadComponentContent({ forced: true, reason: 'INIT_LOAD_FROM_CACHE' }); } }, reloadComponentContent(opts = {}) { const kind = this.get('lib-kind'); const id = this.get('lib-id'); const reason = opts.reason || (opts.forced ? 'FORCED_INTERNAL' : 'EVENT_CHANGE'); log(`RELOAD START (${reason}). Kind: ${kind}, ID: ${id}.`, '#8A2BE2'); if (!kind || !id) { log('RELOAD FEHLER: lib-kind oder lib-id fehlt. Setze Fehler-Placeholder.', '#dc3545', 'error', true); // 💡 FIX: Setze reinen HTML-String als content this.set('content', '
🛑 Fehler: API-Referenz unvollständig.
'); return; } const item = this.getCachedApiItem(kind, id); if (item && (item.html || item.content)) { const content = item.html || item.content; // 💡 FIX: Verwende set('content', ...) statt components(...) // Dadurch wird der Inhalt als reiner HTML-String in die Komponente gesetzt // und nicht als neue, bearbeitbare GrapesJS-Komponenten geparst. this.set('content', content); log(`INHALT erfolgreich für ${kind}/${id} geladen und als REINER HTML-STRING gesetzt.`, '#008000'); } else { log(`RELOAD FEHLER: Inhalt für ${kind}/${id} NICHT im Cache gefunden.`, '#dc3545', 'error', true); // 💡 FIX: Setze reinen HTML-HTML-String als content this.set('content', `
🛑 Fehler: Inhalt für ${kind}/${id} nicht im Cache gefunden.
`); } }, }, { isComponent: el => el && el.nodeType === 1 && el.hasAttribute('lib-id'), extend: 'default', model: { defaults: { ...defaultType.model.prototype.defaults, // 🛑 KRITISCHE FIXES FÜR REFERENZEN components: '', // Darf keine Unterkomponenten haben, die geparst werden editable: false, // ❌ Nicht bearbeitbar (Inline-Editierung verhindern) removable: true, draggable: true, copyable: true, droppable: false, // ❌ Darf keine anderen Komponenten aufnehmen // --------------------------------- traits: [ { type: 'text', name: 'lib-id', label: 'Library ID', changeProp: true }, { type: 'text', name: 'lib-kind', label: 'Library Kind', changeProp: true }, ], 'lib-id': '', 'lib-kind': '', startContent: '', content: '', // Inhalt, der das gerenderte HTML hält } } }), // 💡 WICHTIG: Die View muss den Content als reinen HTML-Inhalt rendern (defaultType macht das). view: defaultType.view,  }); log(`Komponententyp '${REFERENCE_COMPONENT_TYPE}' ASYNCHRON registriert.`, '#008000'); }, 0); }; // -------------------------------------------------------- // (3) HINZUGEFÜGT: Speichern-Befehl (Command) // -------------------------------------------------------- const registerSaveCommand = (editor) => { editor.Commands.add('save-data', { run: function(editor, sender) { // 💡 FIX: Sicherstellen, dass sender existiert und die 'set'-Methode hat (nur bei Buttons) if (sender && typeof sender.set === 'function') { sender.set('active', 0); // Schaltet den Button nach dem Klick ab } if (!CURRENT_ENTITY_ID) { log('SAVE ABORT', 'Speichern abgebrochen: Keine Entity ID verfügbar (B.CURRENT_ENTITY_ID fehlt oder ist 0).', 'red', 'error', true); alert('Speichern fehlgeschlagen: Die ID des aktuellen Elements fehlt.'); return; } // 1. Daten extrahieren const htmlContent = editor.getHtml() + ''; // 2. KRITISCH: Holt die JSON-Repräsentation des Editors const jsonProjectData = editor.getProjectData(); const resource = EDITOR_MODE; const action = `${resource}.update`; log('SAVE START', 'Starte Speichern des Inhalts an die API...', '#FF4500'); // 3. Daten für den POST-Request vorbereiten const dataToSend = { action, id: CURRENT_ENTITY_ID, html: htmlContent, // 🚨 KRITISCH: Server erwartet das Feld 'json' json: jsonProjectData, }; if (B.CURRENT_ENTITY_NAME) { dataToSend.name = B.CURRENT_ENTITY_NAME; } // 4. API-Aufruf (fetch) fetch(API_KERNEL_URL, { method: 'POST', headers: { // Wichtig: JSON-Daten senden 'Content-Type': 'application/json',  }, body: JSON.stringify(dataToSend), }) .then(response => { if (!response.ok) { log('SAVE FAILED (HTTP)', `Speichern fehlgeschlagen: HTTP-Status ${response.status}.`, 'red', 'error', true); throw new Error(`HTTP Error: ${response.status}`); } return response.json(); }) .then(data => { if (data.ok === false) { log('SAVE FAILED (API)', `Speichern fehlgeschlagen: API-Fehler: ${data.error || 'Unbekannt'}`, 'red', 'error', true); alert(`Speichern fehlgeschlagen: ${data.error || 'API-Fehler'}`); } else { log('SAVE SUCCESS', `Speichern erfolgreich für Aktion ${action}.`, '#008000', 'info', true); // 💡 HINZUGEFÜGT: Bestätigung an das Elternfenster senden window.parent.postMessage({ source: 'editor', type: 'save:success' }, '*'); editor.refresh(); // Optional: Editor-Ansicht aktualisieren } }) .catch(error => { log('SAVE FAILED (FETCH)', `FEHLER beim Speichern: ${error.message}`, 'red', 'error', true); alert('Speichern fehlgeschlagen. Netzwerk- oder JSON-Parse-Fehler.'); }); } }); // Eventuell den Button in der Toolbar registrieren (falls noch nicht geschehen) editor.Panels.addButton('options', { id: 'save-data', className: 'fa fa-floppy-o', command: 'save-data',  attributes: { title: 'Speichern (Strg/Cmd + S)' } }); // Tastenkürzel für Speichern hinzufügen editor.Keymaps.add('ctrl-s', 'save-data', 'ctrl+s'); editor.Keymaps.add('cmd-s', 'save-data', 'cmd+s'); log('Speichern-Command und Button/Keymap registriert.', '#FF4500'); }; // -------------------------------------------------------- // (4) Plugin-Funktion (AKTUALISIERT) // -------------------------------------------------------- const plugin = (editor) => { preRegisterCategoriesAndPlaceholders(editor); registerReferenceComponent(editor); registerSaveCommand(editor); // HINZUGEFÜGT: Speichern-Logik editor.on('load', () => { log("GrapesJS 'load' Event: Delegiere asynchrones Laden der API-Blöcke an library-api.", '#1E90FF'); if (B.loadAndRegisterApiBlocks) {  setTimeout(() => { B.loadAndRegisterApiBlocks(editor); }, 500); } else { log(`FEHLER: B.loadAndRegisterApiBlocks ist nicht definiert. library-api.js wurde nicht geladen oder nicht richtig initialisiert.`, 'red', 'error', true); editor.BlockManager.remove(PLACEHOLDER_ID); } }); }; // -------------------------------------------------------- // (5) Export an Bridge Core (unverändert) // -------------------------------------------------------- if (B.registerGrapesJSPlugin) { B.registerGrapesJSPlugin(PluginName, plugin); log(`PLUGIN REGISTER: '${PluginName}' zur Bridge Plugin Registry hinzugefügt.`, '#008000'); } else { log(`FEHLER: B.registerGrapesJSPlugin fehlt. Plugin-Registrierung gescheitert.`, 'red', 'error', true); } })(window.BridgeParts || (window.BridgeParts = {}));