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

271 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/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);
B.EDITOR_MODE = (qs.get('mode') || 'templates').toUpperCase();
log(`START: SKRIPT-AUSFÜHRUNG GESTARTET. Editor Modus: ${B.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 = 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 || '/api/ApiKernel.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: '<div style="padding: 10px; color: #1e3a8a; background-color: #eef2ff; border: 1px solid #c7d2fe; text-align: center;">⚙️ Custom-Blöcke werden geladen...</div>',
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', '<div style="padding: 10px; color: #dc3545; background-color: #fce7f3; border: 1px solid #fbcfe8; text-align: center;">🛑 Fehler: API-Referenz unvollständig.</div>');
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', `<div style="padding: 10px; color: #dc3545; background-color: #fce7f3; border: 1px solid #fbcfe8; text-align: center;">🛑 Fehler: Inhalt für ${kind}/${id} nicht im Cache gefunden.</div>`);
}
},
}, {
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() + '<style>' + editor.getCss() + '</style>';
// 2. KRITISCH: Holt die JSON-Repräsentation des Editors
const jsonProjectData = editor.getProjectData(); 
log('SAVE START', 'Starte Speichern des Inhalts an die API...', '#FF4500');
// 3. Daten für den POST-Request vorbereiten
const dataToSend = {
action: 'blocks.update', // Oder 'templates.update', je nach Entity
id: CURRENT_ENTITY_ID, 
html: htmlContent,
// 🚨 KRITISCH: Korrigiert auf 'json_content' für das PHP-Backend
json_content: jsonProjectData, 
name: B.CURRENT_ENTITY_NAME || 'Unbenannt', // Optional
};
// 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. JSON-Daten wurden gesendet.', '#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 = {}));