612 lines
21 KiB
JavaScript
612 lines
21 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();
|
||
const SECTION_ID = Number(qs.get('section_id') || B.CURRENT_SECTION_ID || 0);
|
||
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 rehydrateLegacyReferences = (editor) => {
|
||
try {
|
||
const wrapper = editor.DomComponents?.getWrapper?.();
|
||
if (!wrapper) return;
|
||
|
||
const candidates = wrapper.find('[data-lib-kind][data-lib-id]');
|
||
if (!candidates || !candidates.length) return;
|
||
|
||
let patched = 0;
|
||
|
||
candidates.forEach((component) => {
|
||
const attrs = (typeof component.getAttributes === 'function') ? (component.getAttributes() || {}) : {};
|
||
const attrKind = component.get('lib-kind') || attrs['data-lib-kind'] || '';
|
||
const attrId = component.get('lib-id') || attrs['data-lib-id'] || '';
|
||
if (!attrKind || !attrId) return;
|
||
|
||
if (typeof component.syncReferenceAttributes === 'function') {
|
||
component.syncReferenceAttributes();
|
||
} else if (component.get && component.set && component.get('type') !== REFERENCE_COMPONENT_TYPE) {
|
||
const parent = component.parent && component.parent();
|
||
if (!parent || typeof parent.components !== 'function') return;
|
||
|
||
const atIndex = parent.components().indexOf(component);
|
||
const startContent = typeof component.toHTML === 'function' ? component.toHTML() : '';
|
||
const classes = component.get && typeof component.get === 'function'
|
||
? (component.get('classes') || [])
|
||
: [];
|
||
const normalizedClasses = Array.isArray(classes)
|
||
? classes
|
||
: (classes.models || classes.collection || []);
|
||
|
||
const newComponent = {
|
||
type: REFERENCE_COMPONENT_TYPE,
|
||
'lib-kind': attrKind,
|
||
'lib-id': attrId,
|
||
startContent,
|
||
attributes: {
|
||
...attrs,
|
||
'data-lib-kind': attrKind,
|
||
'data-lib-id': attrId,
|
||
'data-lib-ref': attrs['data-lib-ref'] || '1',
|
||
},
|
||
};
|
||
|
||
if (normalizedClasses && normalizedClasses.length) {
|
||
newComponent.classes = normalizedClasses.map((cls) => {
|
||
if (typeof cls === 'string') return { name: cls };
|
||
if (cls && typeof cls.get === 'function') return { name: cls.get('name') };
|
||
if (cls && cls.name) return { name: cls.name };
|
||
return null;
|
||
}).filter(Boolean);
|
||
}
|
||
|
||
component.remove();
|
||
parent.components().add(newComponent, { at: atIndex });
|
||
patched++;
|
||
}
|
||
});
|
||
|
||
if (patched) {
|
||
log(`REHYDRATE`, `${patched} Legacy-Referenzen in Referenz-Komponenten umgewandelt.`, '#228B22');
|
||
}
|
||
} catch (error) {
|
||
log('REF REHYDRATE ERROR', error?.message || String(error), '#dc3545', 'error', true);
|
||
}
|
||
};
|
||
|
||
const registerReferenceComponent = (editor) => {
|
||
const domc = editor.DomComponents;
|
||
const defaultType = domc.getType('default');
|
||
|
||
if (!defaultType) return;
|
||
|
||
log(`Starte Registrierung des Komponententyps '${REFERENCE_COMPONENT_TYPE}'.`, '#1E90FF');
|
||
|
||
const referenceRegistry = {};
|
||
const makeKey = (kind, id) => (kind && id) ? `${kind}::${id}` : null;
|
||
const registerReference = (key, model) => {
|
||
if (!key || !model) return;
|
||
if (!referenceRegistry[key]) referenceRegistry[key] = new Set();
|
||
referenceRegistry[key].add(model);
|
||
};
|
||
const unregisterReference = (key, model) => {
|
||
if (!key) return;
|
||
const set = referenceRegistry[key];
|
||
if (!set) return;
|
||
set.delete(model);
|
||
if (set.size === 0) delete referenceRegistry[key];
|
||
};
|
||
const cascadeReferenceUpdate = (kind, id, source) => {
|
||
const key = makeKey(kind, id);
|
||
if (!key) return;
|
||
const set = referenceRegistry[key];
|
||
if (!set) return;
|
||
set.forEach((model) => {
|
||
if (!model || model === source) return;
|
||
if (typeof model.reloadComponentContent === 'function') {
|
||
model.reloadComponentContent({ forced: true, reason: 'CASCADE', skipCascade: true });
|
||
}
|
||
});
|
||
};
|
||
|
||
setTimeout(() => {
|
||
const ReferenceModel = defaultType.model.extend({
|
||
|
||
initialize(props = {}, opts = {}) {
|
||
defaultType.model.prototype.initialize.apply(this, [props, opts]);
|
||
|
||
this.applyReferenceDefaults();
|
||
|
||
this.on('change:lib-kind change:lib-id', () => {
|
||
this.ensureReferenceMetadata();
|
||
this.updateReferenceRegistration();
|
||
this.reloadComponentContent();
|
||
});
|
||
|
||
this.ensureReferenceMetadata();
|
||
this.updateReferenceRegistration();
|
||
this.on('destroy remove', () => this.unregisterReferenceInstance());
|
||
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}.`, '#8A2BE2');
|
||
|
||
if (startContent) {
|
||
this.setPreviewHtml(startContent);
|
||
this.unset('startContent');
|
||
} else if (kind && id) {
|
||
this.reloadComponentContent({ forced: true, reason: 'INIT_LOAD' });
|
||
}
|
||
},
|
||
|
||
applyReferenceDefaults() {
|
||
const enforced = {
|
||
editable: false,
|
||
removable: true,
|
||
draggable: true,
|
||
copyable: true,
|
||
droppable: false,
|
||
'lib-id': this.get('lib-id') || '',
|
||
'lib-kind': this.get('lib-kind') || '',
|
||
rawHtml: this.get('rawHtml') || '',
|
||
};
|
||
|
||
Object.entries(enforced).forEach(([key, value]) => {
|
||
if (typeof this.get(key) === 'undefined') {
|
||
this.set(key, value, { silent: true });
|
||
}
|
||
});
|
||
|
||
const traits = this.get('traits');
|
||
const hasLibTraits = Array.isArray(traits) && traits.some(t => t?.name === 'lib-id' || t?.name === 'lib-kind');
|
||
if (!hasLibTraits) {
|
||
this.set('traits', [
|
||
{ type: 'text', name: 'lib-id', label: 'Library ID', changeProp: true },
|
||
{ type: 'text', name: 'lib-kind', label: 'Library Kind', changeProp: true },
|
||
], { silent: true });
|
||
}
|
||
|
||
const comps = this.components?.();
|
||
if (comps && typeof comps.reset === 'function' && comps.length) {
|
||
comps.reset([]);
|
||
}
|
||
},
|
||
|
||
ensureReferenceMetadata() {
|
||
const attrsCurrent = this.get('attributes') || {};
|
||
let attrs = Array.isArray(attrsCurrent) ? {} : { ...attrsCurrent };
|
||
const kind = this.get('lib-kind') || attrs['data-lib-kind'] || '';
|
||
const id = this.get('lib-id') || attrs['data-lib-id'] || '';
|
||
let changed = false;
|
||
|
||
if (!this.get('lib-kind') && kind) {
|
||
this.set('lib-kind', kind, { silent: true });
|
||
}
|
||
if (!this.get('lib-id') && id) {
|
||
this.set('lib-id', id, { silent: true });
|
||
}
|
||
|
||
if (attrs['data-lib-kind'] !== kind) {
|
||
attrs['data-lib-kind'] = kind;
|
||
changed = true;
|
||
}
|
||
if (attrs['data-lib-id'] !== id) {
|
||
attrs['data-lib-id'] = id;
|
||
changed = true;
|
||
}
|
||
if (attrs['data-lib-ref'] !== '1') {
|
||
attrs['data-lib-ref'] = '1';
|
||
changed = true;
|
||
}
|
||
|
||
if (changed) {
|
||
this.set('attributes', attrs);
|
||
}
|
||
},
|
||
|
||
getReferenceKey() {
|
||
return makeKey(this.get('lib-kind'), this.get('lib-id'));
|
||
},
|
||
|
||
updateReferenceRegistration() {
|
||
const nextKey = this.getReferenceKey();
|
||
if (this._refKey === nextKey) return;
|
||
if (this._refKey) unregisterReference(this._refKey, this);
|
||
if (nextKey) registerReference(nextKey, this);
|
||
this._refKey = nextKey;
|
||
},
|
||
|
||
unregisterReferenceInstance() {
|
||
if (this._refKey) {
|
||
unregisterReference(this._refKey, this);
|
||
this._refKey = null;
|
||
}
|
||
},
|
||
|
||
getCachedApiItem(kind, id) {
|
||
const key = `${kind}-${id}`;
|
||
const item = B.ApiItemCache?.[key];
|
||
return item || null;
|
||
},
|
||
|
||
fetchReference(kind, id) {
|
||
if (!kind || !id) return Promise.resolve(null);
|
||
const key = `${kind}-${id}`;
|
||
const cached = this.getCachedApiItem(kind, id);
|
||
if (cached && cached.html) return Promise.resolve(cached);
|
||
if (typeof B.getApiItem !== 'function') return Promise.resolve(cached);
|
||
return B.getApiItem(kind, id)
|
||
.then((data) => {
|
||
if (!data) return cached;
|
||
const normalized = {
|
||
html: data.html || data.item?.html || '',
|
||
content: data.content || data.item?.content || '',
|
||
};
|
||
B.ApiItemCache = B.ApiItemCache || {};
|
||
B.ApiItemCache[key] = { ...(B.ApiItemCache[key] || {}), ...normalized };
|
||
return B.ApiItemCache[key];
|
||
})
|
||
.catch(() => cached);
|
||
},
|
||
|
||
renderError(message) {
|
||
return `<div class="lib-ref-placeholder">${message}</div>`;
|
||
},
|
||
|
||
setPreviewHtml(html) {
|
||
const safeHtml = html || this.renderError('Referenz lädt …');
|
||
const alreadyDecorated = /^\s*<[^>]+data-lib-ref="1"/i.test(safeHtml);
|
||
let decoratedHtml = safeHtml;
|
||
if (!alreadyDecorated) {
|
||
const kind = this.get('lib-kind') || '';
|
||
const id = this.get('lib-id') || '';
|
||
const attrs = [
|
||
'data-lib-ref="1"',
|
||
kind ? `data-lib-kind="${kind}"` : '',
|
||
id ? `data-lib-id="${id}"` : '',
|
||
].filter(Boolean).join(' ');
|
||
decoratedHtml = `<div class="lib-ref-wrapper" ${attrs}>${safeHtml}</div>`;
|
||
}
|
||
this.set('rawHtml', decoratedHtml);
|
||
const comps = this.components();
|
||
if (comps && comps.length) comps.reset([]);
|
||
this.trigger('preview:update');
|
||
},
|
||
populatePlaceholder(el) {
|
||
if (!el || el.__libHydrated) return;
|
||
const kind = el.getAttribute('data-lib-kind');
|
||
const id = el.getAttribute('data-lib-id');
|
||
if (!kind || !id) return;
|
||
el.setAttribute('data-lib-ref', '1');
|
||
const applyHtml = (html) => {
|
||
if (typeof html === 'string' && html.length) {
|
||
el.innerHTML = html;
|
||
this.decoratePlaceholder(el, kind, id);
|
||
this.hydrateNestedReferences(el);
|
||
el.__libHydrated = true;
|
||
}
|
||
};
|
||
const cached = this.getCachedApiItem(kind, id);
|
||
if (cached && cached.html) {
|
||
applyHtml(cached.html);
|
||
}
|
||
this.fetchReference(kind, id).then((item) => {
|
||
if (item && item.html) applyHtml(item.html);
|
||
});
|
||
},
|
||
|
||
decoratePlaceholder(el, kind, id) {
|
||
el.setAttribute('data-lib-ref', '1');
|
||
if (kind) el.setAttribute('data-lib-kind', kind);
|
||
if (id) el.setAttribute('data-lib-id', id);
|
||
el.setAttribute('contenteditable', 'false');
|
||
el.style.pointerEvents = 'none';
|
||
el.style.userSelect = 'none';
|
||
},
|
||
|
||
hydrateNestedReferences(root) {
|
||
if (!root) return;
|
||
const placeholders = root.querySelectorAll('[data-lib-kind][data-lib-id]');
|
||
placeholders.forEach((node) => this.populatePlaceholder(node));
|
||
},
|
||
|
||
reloadComponentContent(opts = {}) {
|
||
const kind = this.get('lib-kind');
|
||
const id = this.get('lib-id');
|
||
const reason = opts.reason || (opts.forced ? 'FORCED' : 'AUTO');
|
||
log(`RELOAD START (${reason}). Kind: ${kind}, ID: ${id}.`, '#8A2BE2');
|
||
|
||
if (!kind || !id) {
|
||
log('RELOAD FEHLER: lib-kind oder lib-id fehlt.', '#dc3545', 'error', true);
|
||
this.setPreviewHtml(this.renderError('🛑 Fehler: Referenz unvollständig.'));
|
||
return;
|
||
}
|
||
|
||
const cached = this.getCachedApiItem(kind, id);
|
||
if (cached && cached.html) {
|
||
this.setPreviewHtml(cached.html);
|
||
}
|
||
|
||
this.fetchReference(kind, id)
|
||
.then((item) => {
|
||
if (item && item.html) {
|
||
this.setPreviewHtml(item.html);
|
||
log(`INHALT erfolgreich für ${kind}/${id} geladen.`, '#008000');
|
||
if (!opts.skipCascade) {
|
||
cascadeReferenceUpdate(kind, id, this);
|
||
}
|
||
} else {
|
||
log(`RELOAD FEHLER: Inhalt ${kind}/${id} nicht gefunden.`, '#dc3545', 'error', true);
|
||
this.setPreviewHtml(
|
||
this.renderError(`🛑 Fehler: Inhalt für ${kind}/${id} nicht gefunden.`)
|
||
);
|
||
}
|
||
})
|
||
.catch((error) => {
|
||
log('RELOAD FETCH ERROR', error?.message || String(error), '#dc3545', 'error', true);
|
||
this.setPreviewHtml(this.renderError('🛑 Fehler beim Laden der Referenz.'));
|
||
});
|
||
},
|
||
|
||
toHTML(opts = {}) {
|
||
const raw = this.get('rawHtml');
|
||
if (raw) return raw;
|
||
return defaultType.model.prototype.toHTML.call(this, opts);
|
||
},
|
||
|
||
syncReferenceAttributes() {
|
||
this.ensureReferenceMetadata();
|
||
},
|
||
}, {
|
||
isComponent: (el) => el && el.nodeType === 1 && el.hasAttribute('lib-id'),
|
||
});
|
||
|
||
const ReferenceView = defaultType.view.extend({
|
||
initialize(opts = {}) {
|
||
defaultType.view.prototype.initialize.apply(this, [opts]);
|
||
this.listenTo(this.model, 'preview:update', this.renderPreview);
|
||
},
|
||
|
||
render() {
|
||
defaultType.view.prototype.render.apply(this, arguments);
|
||
this.el.classList.add('lib-ref');
|
||
this.renderPreview();
|
||
return this;
|
||
},
|
||
|
||
renderPreview() {
|
||
const html = this.model.get('rawHtml') || this.model.renderError('Referenz lädt …');
|
||
this.el.innerHTML = '';
|
||
const wrap = document.createElement('div');
|
||
wrap.className = 'lib-ref-inner';
|
||
wrap.innerHTML = html;
|
||
wrap.setAttribute('contenteditable', 'false');
|
||
wrap.style.pointerEvents = 'none';
|
||
wrap.style.userSelect = 'none';
|
||
this.el.appendChild(wrap);
|
||
this.model.hydrateNestedReferences(wrap);
|
||
},
|
||
});
|
||
|
||
domc.addType(REFERENCE_COMPONENT_TYPE, {
|
||
model: ReferenceModel,
|
||
view: ReferenceView,
|
||
});
|
||
|
||
log(`Komponententyp '${REFERENCE_COMPONENT_TYPE}' 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 fontCss = (B && typeof B.RTE_FONT_FACE_CSS === 'string' && B.RTE_FONT_FACE_CSS.trim())
|
||
? B.RTE_FONT_FACE_CSS.trim()
|
||
: '';
|
||
const cssPayload = (fontCss ? fontCss + '\n' : '') + editor.getCss();
|
||
const htmlContent = editor.getHtml() + '<style>' + cssPayload + '</style>';
|
||
// 2. KRITISCH: Holt die JSON-Repräsentation des Editors
|
||
let jsonProjectDataRaw = '';
|
||
try {
|
||
const jsonProjectData = editor.getProjectData();
|
||
jsonProjectDataRaw = JSON.stringify(jsonProjectData);
|
||
} catch (e) {
|
||
console.error('[bridge-blocks-api] getProjectData stringify failed', e);
|
||
jsonProjectDataRaw = '';
|
||
}
|
||
|
||
const resource = 'content';
|
||
const action = `${resource}.update`;
|
||
const debugSave = (() => {
|
||
try {
|
||
const params = new URLSearchParams(window.location.search || '');
|
||
if (params.get('debug_save') === '1') return true;
|
||
return localStorage.getItem('et_debug_save') === '1';
|
||
} catch (e) {
|
||
return false;
|
||
}
|
||
})();
|
||
|
||
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: jsonProjectDataRaw,
|
||
};
|
||
const activateNext = B.NEXT_ACTIVATE_VERSION || window.NEXT_ACTIVATE_VERSION;
|
||
if (activateNext) {
|
||
dataToSend.activate_version = 1;
|
||
B.NEXT_ACTIVATE_VERSION = 0;
|
||
window.NEXT_ACTIVATE_VERSION = 0;
|
||
}
|
||
if (SECTION_ID) {
|
||
dataToSend.section_id = SECTION_ID;
|
||
}
|
||
if (debugSave) {
|
||
dataToSend.debug = 1;
|
||
console.log('[ET DEBUG] save-data payload', {
|
||
id: CURRENT_ENTITY_ID,
|
||
mode: resource,
|
||
htmlLength: htmlContent.length,
|
||
jsonLength: jsonProjectDataRaw.length,
|
||
htmlPreview: htmlContent.slice(0, 200),
|
||
jsonPreview: jsonProjectDataRaw.slice(0, 200),
|
||
});
|
||
}
|
||
|
||
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');
|
||
// 💡 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('save-data-ctrl', 'ctrl+s', 'save-data');
|
||
editor.Keymaps.add('save-data-cmd', 'cmd+s', 'save-data');
|
||
|
||
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', () => {
|
||
rehydrateLegacyReferences(editor);
|
||
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 = {}));
|