279 lines
11 KiB
JavaScript
279 lines
11 KiB
JavaScript
/* /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: '<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();
|
||
|
||
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 = {}));
|