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