commit
This commit is contained in:
496
public/editor/bridge-core (Kopie).js
Normal file
496
public/editor/bridge-core (Kopie).js
Normal file
@@ -0,0 +1,496 @@
|
||||
/* /editor/bridge-core.js — Loader + Orchestrator (FINAL & LOG-KONTROLLIERT) */
|
||||
(function () {
|
||||
|
||||
// --- Initialisierung BridgeParts (B) und Plugin-Registry ---
|
||||
if (!window.BridgeParts) window.BridgeParts = {};
|
||||
const B = window.BridgeParts;
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// 🎯 LOKALE LOG-KONFIGURATION & WRAPPER
|
||||
// ----------------------------------------------------------------------
|
||||
const PluginName = 'bridge-core';
|
||||
|
||||
// Setzen Sie dies auf 'false', um alle Logs NUR für dieses Plugin zu deaktivieren.
|
||||
if (B.LOG_CONFIG) {
|
||||
B.LOG_CONFIG.PLUGINS[PluginName] = false; // bridge-core spezifisch deaktivieren (optional)
|
||||
}
|
||||
|
||||
/**
|
||||
* NEUER LOKALER WRAPPER, der die zentrale B.log Funktion verwendet.
|
||||
* Der unformatierte Fallback WURDE ENTFERNT, da er das console.log erzeugt hat.
|
||||
* Die ersten kritischen Logs WURDEN EBENFALLS ENTFERNT, da sie vor B.log lagen.
|
||||
*/
|
||||
const log = (type, message, color = '#1E90FF', logType = 'info', force = false) => {
|
||||
// Loggt NUR, wenn B.log verfügbar ist (aus general-functions.js).
|
||||
if (typeof B.log === 'function') {
|
||||
B.log(PluginName, `[${type}] ${message}`, color, logType, force);
|
||||
}
|
||||
// Ansonsten wird NICHTS geloggt, bis general-functions.js geladen ist.
|
||||
};
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
// 🛑 GLOBALER LOG ZUR BESTÄTIGUNG DER SKRIPT-AUSFÜHRUNG
|
||||
// Dieser erste Log-Aufruf wird nun still ignoriert, da B.log noch fehlt.
|
||||
// Er wird durch den SUCCESS-Log der general-functions.js ersetzt.
|
||||
// log('START', `SKRIPT-AUSFÜHRUNG GESTARTET.`, '#DC143C', 'info', true); // DEAKTIVIERT/IGNORIERT DURCH FEHLENDEN B.log
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// 🛑 KONFIGURATION: NEWSLETTER-PRESET-TOGGLE
|
||||
// ----------------------------------------------------------------------
|
||||
const LOAD_NEWSLETTER_PRESET = false; // <<< KRITISCHER FIX: Auf FALSE gesetzt, um den "defaults" Konflikt zu beheben!
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
if (window.__bridgeCoreInitialized) {
|
||||
log('INIT ABORT', 'Bridge Core wurde bereits initialisiert.', 'orange');
|
||||
return;
|
||||
}
|
||||
window.__bridgeCoreInitialized = true;
|
||||
|
||||
// --- Initialisierung BridgeParts (B) und Plugin-Registry ---
|
||||
B.BASE_PATH_BRIDGE = '../assets/js/bridge/';
|
||||
B.BASE_PATH_CONFIG = B.BASE_PATH_BRIDGE;
|
||||
|
||||
B.GrapesJSPlugins = [];
|
||||
|
||||
B.registerGrapesJSPlugin = (name, pluginFn) => {
|
||||
B.GrapesJSPlugins.push({ name, pluginFn });
|
||||
log('PLUGIN REGISTER', `Plugin zur Registry hinzugefügt: ${name}`, 'yellow');
|
||||
};
|
||||
|
||||
// --- DEBUG-HELPER UND LOADER-HELPER ---
|
||||
const badgeSay = (text, type = 'info') => {
|
||||
const b=document.getElementById('badge');
|
||||
if (!b) return;
|
||||
b.textContent = text;
|
||||
switch(type) {
|
||||
case 'ok': b.style.background = '#dcfce7'; b.style.color = '#15803d'; b.style.borderColor = '#bbf7d0'; break;
|
||||
case 'error': b.style.background = '#fee2e2'; b.style.color = '#7f1d1d'; b.style.borderColor = '#fecaca'; break;
|
||||
default: b.style.background = '#eef2ff'; b.style.color = '#1e3a8a'; b.style.borderColor = '#c7d2fe';
|
||||
}
|
||||
};
|
||||
|
||||
function loadScript(url, done) {
|
||||
const filename = url.split('/').pop();
|
||||
var s = document.createElement('script');
|
||||
s.src = url + (url.indexOf('?') === -1 ? '?v=' : '&v=') + Date.now();
|
||||
s.async = false;
|
||||
|
||||
s.onload = function(){
|
||||
log('LOAD SUCCESS', `Skript geladen: ${filename}`, 'green');
|
||||
try {
|
||||
done && done();
|
||||
} catch(e){
|
||||
if (e.message.includes('setting getter-only property "defaults"')) {
|
||||
log('RUNTIME WARNING', `IGNORIERE Block-Konflikt in ${filename}: ${e.message}`, 'orange', 'warn');
|
||||
} else {
|
||||
// 🛑 KORREKTUR: force: true explizit auf false setzen (oder weglassen)
|
||||
log('RUNTIME ERROR', `Fehler in Callback nach ${filename}: ${e.message}`, 'red', 'error', false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
s.onerror = function(){
|
||||
// 🛑 KORREKTUR: force: true explizit auf false setzen (oder weglassen)
|
||||
log('LOAD FAILED', `Skript FEHLT oder Pfad falsch: ${filename}`, 'red', 'error', false);
|
||||
badgeSay(`Ladefehler: ${filename}`, 'error');
|
||||
try { done && done(); } catch(e){}
|
||||
};
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* HILFSFUNKTION: Wandelt den Dateinamen (z.B. blocks-standard.js) in den globalen
|
||||
* Objektnamen (z.B. BridgeBlocksStandard) um.
|
||||
*/
|
||||
function getPluginObjectName(fileName) {
|
||||
// 1. Entferne Dateiendung (.js)
|
||||
let name = fileName.replace('.js', ''); // 'blocks-standard'
|
||||
|
||||
// 2. Teile in Bestandteile zerlegen und den ersten Buchstaben groß schreiben
|
||||
const parts = name.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)); // ['Blocks', 'Standard']
|
||||
|
||||
// 3. Mit 'Bridge' prefixen und zusammenfügen
|
||||
return 'Bridge' + parts.join(''); // 'BridgeBlocksStandard'
|
||||
}
|
||||
|
||||
// 🛑 NEUE FUNKTION: Erstellt alle in der Konfiguration definierten Kategorien.
|
||||
function ensureConfiguredCategories(editor) {
|
||||
const bm = editor.BlockManager;
|
||||
const config = window.BridgeParts?.CATEGORY_CONFIG || {};
|
||||
|
||||
Object.keys(config)
|
||||
.sort((a, b) => (config[a].ord || 999) - (config[b].ord || 999))
|
||||
.forEach(catId => {
|
||||
const catConf = config[catId];
|
||||
// Category wird nur erstellt, wenn sie noch nicht existiert
|
||||
if (!bm.getCategories().get(catId)) {
|
||||
bm.getCategories().add({
|
||||
id: catId,
|
||||
label: catConf.label,
|
||||
open: catConf.open !== false,
|
||||
order: catConf.ord || 999
|
||||
});
|
||||
log('CAT INIT', `Kategorie '${catId}' explizit erstellt.`, 'green');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadBridgeParts(cb){
|
||||
const base = B.BASE_PATH_BRIDGE;
|
||||
|
||||
// 🛑 LOKALES LOGGING ENTFERNT, DA ES VOR B.log LIEGT.
|
||||
// log('LOAD START', 'Starte Laden der modularen Bridge-Teile (Geordnet).');
|
||||
|
||||
const coreFiles = [
|
||||
// base + 'category-config.js',
|
||||
// base + 'general-functions.js',
|
||||
base + 'library-parts.js',
|
||||
base + 'categorization-master.js',
|
||||
base + 'categorization-cleanup.js',
|
||||
];
|
||||
|
||||
const initialLoadList = [...coreFiles];
|
||||
|
||||
function recursiveLoader(list, index = 0) {
|
||||
if (index >= list.length) {
|
||||
log('LOAD END', 'Initial-Bridge-Skripte geladen.', 'green');
|
||||
|
||||
const config = window.BridgeParts?.CATEGORY_CONFIG || {};
|
||||
let allBlockFiles = [];
|
||||
|
||||
// Dynamisches Sammeln der Block-Dateien aus der Config
|
||||
Object.keys(config)
|
||||
.sort((a, b) => (config[a].ord || 999) - (config[b].ord || 999))
|
||||
.forEach(key => {
|
||||
// Sammelt alle Dateien, egal ob sync oder async
|
||||
if (Array.isArray(config[key].files)) {
|
||||
allBlockFiles.push(...config[key].files.map(file => base + file));
|
||||
}
|
||||
});
|
||||
|
||||
// Duplikate entfernen (falls eine Datei in mehreren Kategorien gelistet ist)
|
||||
allBlockFiles = Array.from(new Set(allBlockFiles));
|
||||
|
||||
function loadBlockFiles(blockIndex = 0) {
|
||||
if (blockIndex >= allBlockFiles.length) {
|
||||
log('LOAD END', 'Alle Blöcke geladen.', 'green');
|
||||
return cb && cb(B);
|
||||
}
|
||||
log('LOADING BLOCKS', `Lade Block-Skript [${blockIndex + 1}/${allBlockFiles.length}]: ${allBlockFiles[blockIndex].split('/').pop()}`);
|
||||
loadScript(allBlockFiles[blockIndex], function(){
|
||||
loadBlockFiles(blockIndex + 1);
|
||||
});
|
||||
}
|
||||
loadBlockFiles();
|
||||
return;
|
||||
}
|
||||
|
||||
// 🛑 LOKALES LOGGING ENTFERNT, DA ES VOR B.log LIEGT.
|
||||
log('LOADING CORE', `Lade Skript [${index + 1}/${initialLoadList.length}]: ${list[index].split('/').pop()}`); // Loggt ab dem 3. Skript, da general-functions.js an 2. Stelle geladen wird.
|
||||
|
||||
loadScript(list[index], function(){
|
||||
recursiveLoader(list, index + 1);
|
||||
});
|
||||
}
|
||||
|
||||
recursiveLoader(initialLoadList);
|
||||
}
|
||||
|
||||
try { parent.postMessage({ source:'bridge', type:'boot' }, '*'); } catch {}
|
||||
var MODE = (window.__editorMode || 'templates').toLowerCase();
|
||||
|
||||
const replaceReferenceLibrary = (editor, ref, mode) => {
|
||||
(window.BridgeParts?.addReferenceLibrary || (()=>{}))(editor, ref, mode);
|
||||
};
|
||||
const upsertCustomForBothCats = (editor, payload) => {
|
||||
(window.BridgeParts?.upsertCustomForBothCats || (()=>{}))(editor, payload);
|
||||
};
|
||||
|
||||
// --- Init & Events (Plugin integriert) ---------------------------------------------
|
||||
loadBridgeParts(function(B){
|
||||
log('INIT START', 'Alle Bridge-Teile geladen, starte GrapesJS-Initialisierung.', 'orange');
|
||||
|
||||
if (typeof grapesjs === 'undefined' || !grapesjs.init) {
|
||||
// 🛑 KORREKTUR: force: true explizit auf false setzen (oder weglassen)
|
||||
log('CRITICAL ERROR', 'Das globale Objekt grapesjs ist nicht verfügbar! Laden von grapes.min.js ist fehlgeschlagen.', 'red', 'error', false);
|
||||
badgeSay('Fehler: GrapesJS nicht geladen!', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// 🛑 KRITISCHER FIX TEIL 1: Registriere alle gesammelten Bridge-Plugins global.
|
||||
if (typeof grapesjs.plugins.add === 'function') {
|
||||
B.GrapesJSPlugins.forEach(p => {
|
||||
grapesjs.plugins.add(p.name, p.pluginFn);
|
||||
log('PLUGIN ACTIVATION', `GrapesJS Plugin global bereitgestellt: ${p.name}`, 'lime');
|
||||
});
|
||||
} else {
|
||||
// 🛑 KORREKTUR: force: true explizit auf false setzen (oder weglassen)
|
||||
log('PLUGIN ERROR', `GrapesJS Plugin-API (grapesjs.plugins.add) fehlt. Plugins können nicht registriert werden.`, 'red', 'error', false);
|
||||
}
|
||||
|
||||
// 🛑 KRITISCHER FIX: Safety Plugin MUSS die fehlenden Views Panels hinzufügen.
|
||||
function safetyPlugin(editor){
|
||||
const pn = editor.Panels, orig = pn.getButton.bind(pn);
|
||||
pn.getButton = (pid, id) => orig(pid, id) || { set(){}, get(){ return null; } };
|
||||
|
||||
// Fügen Sie das Panel 'views' hinzu, wenn es fehlt
|
||||
if(!pn.getPanel('views')) {
|
||||
pn.addPanel({ id: 'views', el: '.gjs-pn-views' });
|
||||
log('PANEL FIX', "Das 'views' Panel wurde nachträglich hinzugefügt.", 'yellow', 'warn');
|
||||
}
|
||||
|
||||
// Stellen Sie sicher, dass der Block Manager in den Views-Container rendert
|
||||
editor.Config.blockManager = editor.Config.blockManager || {};
|
||||
editor.Config.blockManager.appendTo = editor.Config.blockManager.appendTo || '.gjs-blocks';
|
||||
|
||||
// Der fehlerhafte Timeout-Block wurde entfernt.
|
||||
}
|
||||
|
||||
let pluginsList = [
|
||||
safetyPlugin,
|
||||
// 🛑 KRITISCHE ERGÄNZUNG: Aktiviert das registrierte API-Plugin
|
||||
'bridge-blocks-api',
|
||||
'bridge-categorization-master',
|
||||
'bridge-categorization-cleanup',
|
||||
];
|
||||
|
||||
if (LOAD_NEWSLETTER_PRESET) {
|
||||
pluginsList.push('gjs-preset-newsletter');
|
||||
}
|
||||
|
||||
var ed = grapesjs.init({
|
||||
container: '#gjs',
|
||||
height: '100vh',
|
||||
storageManager: false,
|
||||
plugins: pluginsList,
|
||||
pluginsOpts: {},
|
||||
// 🛑 KRITISCHE ERGÄNZUNG: Verhindert das automatische Ausblenden leerer Kategorien
|
||||
blockManager: {
|
||||
hideEmpty: false
|
||||
}
|
||||
});
|
||||
|
||||
window.__gjs = ed;
|
||||
|
||||
// 🛑 KRITISCHE KORREKTUR 1: Explizite Erstellung aller konfigurierten Kategorien
|
||||
ensureConfiguredCategories(ed);
|
||||
|
||||
// 🛑 KRITISCHE KORREKTUR 2: Sofortige Label-Korrektur
|
||||
// Überschreibt den potenziell falschen, durch GrapesJS gesetzten Label-Namen
|
||||
Object.keys(B.CATEGORY_CONFIG || {}).forEach(catId => {
|
||||
const expectedLabel = B.CATEGORY_CONFIG[catId].label;
|
||||
const categoryModel = ed.BlockManager.getCategories().get(catId);
|
||||
|
||||
if (categoryModel && categoryModel.get('label') !== expectedLabel) {
|
||||
// Setzen ohne das 'change:label' Event auszulösen (optional, aber sauber)
|
||||
categoryModel.set('label', expectedLabel, { silent: true });
|
||||
log('LABEL FIX', `Kategorie '${catId}' Label auf korrigiert: '${expectedLabel}'`, 'yellow', 'warn');
|
||||
}
|
||||
});
|
||||
// ---------------------------------------------------
|
||||
|
||||
B.ensureViews && B.ensureViews(ed);
|
||||
|
||||
log('BLOCK REGISTER', 'Registriere Bridge Blöcke, um Preset-Defaults zu überschreiben.', 'purple');
|
||||
|
||||
// 🛑 DYNAMISCHE AKTIVIERUNG DER SYNCHRONEN BLÖCKE (Ersetzt die fixen Aufrufe)
|
||||
if (B.CATEGORY_CONFIG && ed) {
|
||||
log('DYNAMIC ACTIVATION', 'Starte Aktivierung synchroner Block-Plugins (via Config).', 'purple');
|
||||
|
||||
// Iteriere über die konfigurierten Kategorien
|
||||
Object.keys(B.CATEGORY_CONFIG).forEach(catId => {
|
||||
const config = B.CATEGORY_CONFIG[catId];
|
||||
|
||||
// Verarbeite nur SYNCHRONE Plugins, die Dateien angeben
|
||||
if (config.registration_mode === 'sync' && Array.isArray(config.files)) {
|
||||
|
||||
config.files.forEach(fileName => {
|
||||
|
||||
// Korrigierte Funktion liefert jetzt z.B. 'BridgeBlocksCustom'
|
||||
const objectName = getPluginObjectName(fileName);
|
||||
const plugin = window[objectName];
|
||||
|
||||
// Prüfen, ob das Skript geladen wurde und die Register-Funktion vorhanden ist
|
||||
if (plugin && typeof plugin.register === 'function') {
|
||||
log('DYNAMIC ACTIVATION', `Registriere sync Plugin: ${objectName} (${fileName})`, 'lime');
|
||||
try {
|
||||
plugin.register(ed);
|
||||
} catch(e) {
|
||||
log('DYNAMIC ACTIVATION ERROR', `Fehler beim Registrieren von ${objectName}: ${e.message}`, 'red', 'error');
|
||||
}
|
||||
} else {
|
||||
log('DYNAMIC ACTIVATION WARNING', `Sync Plugin Objekt oder .register() Methode nicht gefunden: ${objectName} (${fileName})`, 'orange', 'warn');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
// ---------------------------------------------------
|
||||
|
||||
log('INIT API', 'API-Elemente werden nun durch das Plugin bridge-blocks-api geladen.', 'orange');
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// DEBUGGING: ZÄHLE REKURSIVE EVENTS
|
||||
// ----------------------------------------------------------------------
|
||||
let eventCounts = {};
|
||||
let isParsing = false;
|
||||
const MAX_CALLS = 1000;
|
||||
|
||||
const debugEvents = [
|
||||
'component:add',
|
||||
'component:update',
|
||||
'change:components',
|
||||
'block:add',
|
||||
'change:attributes',
|
||||
'comp:update:status'
|
||||
];
|
||||
|
||||
const debugListener = (event, model) => {
|
||||
if (!isParsing) return;
|
||||
|
||||
if (!eventCounts[event]) {
|
||||
eventCounts[event] = 0;
|
||||
}
|
||||
eventCounts[event]++;
|
||||
|
||||
if (eventCounts[event] === MAX_CALLS + 1) {
|
||||
// Diese kritischen Debug-Meldungen bleiben DIREKT im console-Objekt,
|
||||
// da sie immer sichtbar sein müssen, um Endlosschleifen zu erkennen.
|
||||
console.error(`%c[DEBUG RECURSION ALARM] 🚨 Event '${event}' hat den Grenzwert von ${MAX_CALLS} überschritten!`, 'color:red; font-size: 1.1em; font-weight: bold;');
|
||||
}
|
||||
|
||||
if (eventCounts[event] > MAX_CALLS && eventCounts[event] < (MAX_CALLS + 10)) {
|
||||
const type = (model && typeof model.get === 'function') ? model.get('type') : 'N/A';
|
||||
const parentType = (model && typeof model.parent === 'function' && model.parent()) ? model.parent().get('type') : 'N/A';
|
||||
// Diese bleiben console.log aus demselben Grund
|
||||
console.log(`%c [RECURSION SOURCE] Event: ${event}, Type: ${type}, Parent: ${parentType}`, 'color: #8b0000;');
|
||||
}
|
||||
};
|
||||
|
||||
ed.on('load', function() {
|
||||
debugEvents.forEach(event => ed.on(event, (model) => debugListener(event, model)));
|
||||
|
||||
setTimeout(() => {
|
||||
(B.waitForBlocks ? B.waitForBlocks(ed) : Promise.resolve()).then(function(){
|
||||
try {
|
||||
log('CORE WARN', 'Führe finalen, verzögerten Cleanup-Lauf durch (2000ms).', 'orange', 'warn');
|
||||
|
||||
B.normalizeCategories && B.normalizeCategories(ed);
|
||||
B.renderBlocks && B.renderBlocks(ed);
|
||||
|
||||
} catch(e) {
|
||||
log('CORE ERROR', `Finaler Cleanup-Fehler: ${e.message}`, 'red', 'error');
|
||||
}
|
||||
});
|
||||
}, 2000);
|
||||
|
||||
}, { once: true });
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// MESSAGE HANDLER
|
||||
// ----------------------------------------------------------------------
|
||||
window.addEventListener('message', async function(ev){
|
||||
var data = ev.data || {};
|
||||
if (data.source !== 'admin') return;
|
||||
|
||||
if (data.type === 'init'){
|
||||
B.ensureViews && B.ensureViews(ed);
|
||||
|
||||
var html = (data.html || '').trim();
|
||||
if (!html) html = '<table style="width:100%;font-family:Arial,sans-serif"><tr><td><h1>Neues Dokument</h1><p>Inhalt ...</p></td></tr></table>';
|
||||
|
||||
const applySnips = function(arr){
|
||||
const list = (Array.isArray(arr)?arr:[]).map(s => ({ id:s.id, name:s.name, html: s.html || s.content || '' }));
|
||||
|
||||
B.replaceSnippetBlocks && B.replaceSnippetBlocks(ed, list);
|
||||
|
||||
upsertCustomForBothCats(ed, {
|
||||
ref: (data.ref && (Array.isArray(data.ref.sections) || Array.isArray(data.ref.blocks))) ? {
|
||||
sections: data.ref.sections || [],
|
||||
blocks: data.ref.blocks || []
|
||||
} : { sections: [], blocks: [] },
|
||||
snippets: list
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
try {
|
||||
// Erneutes Normalisieren nach Laden der Snippets (falls nötig)
|
||||
B.normalizeCategories && B.normalizeCategories(ed);
|
||||
B.ensureViews && B.ensureViews(ed);
|
||||
B.renderBlocks && B.renderBlocks(ed);
|
||||
log('CORE WARN', 'normalize/render nach applySnips ausgeführt (1ms).', 'orange', 'warn');
|
||||
} catch(e) {
|
||||
log('CORE ERROR', `applySnips-Cleanup-Fehler: ${e.message}`, 'red', 'error');
|
||||
}
|
||||
}, 1);
|
||||
|
||||
};
|
||||
|
||||
if (Array.isArray(data.snippets) && data.snippets.length) applySnips(data.snippets);
|
||||
else (B.fetchSnippets ? B.fetchSnippets() : Promise.resolve([])).then(applySnips);
|
||||
|
||||
if (data.ref && (Array.isArray(data.ref.sections) || Array.isArray(data.ref.blocks))) {
|
||||
replaceReferenceLibrary(ed, {
|
||||
sections: data.ref.sections || [],
|
||||
blocks: data.ref.blocks || []
|
||||
}, MODE);
|
||||
}
|
||||
|
||||
// Finaler Aufruf nachrichtengesteuert (konsolidiert)
|
||||
setTimeout(() => {
|
||||
(B.waitForBlocks ? B.waitForBlocks(ed) : Promise.resolve()).then(function(){
|
||||
try {
|
||||
log('CORE WARN', 'Führe nachrichtengesteuerten Final-Cleanup-Lauf durch (100ms).', 'orange', 'warn');
|
||||
|
||||
if (!ed.__contentLoaded) {
|
||||
window.__GJS_IS_PARSING = true;
|
||||
isParsing = true;
|
||||
eventCounts = {};
|
||||
|
||||
try {
|
||||
ed.setComponents(html);
|
||||
} catch (e) {
|
||||
log('SET COMPONENTS FAILED', `setComponents Fehler: ${e.message}. Aufgerufene Event-Zähler: ${JSON.stringify(eventCounts)}`, 'red', 'error');
|
||||
// console.table(eventCounts); bleibt eine direkte Debug-Ausgabe
|
||||
throw e;
|
||||
} finally {
|
||||
window.__GJS_IS_PARSING = false;
|
||||
isParsing = false;
|
||||
log('CONTENT', 'HTML-Inhalt in den Editor geladen (FINAL FIX).', 'orange');
|
||||
|
||||
B.normalizeCategories && B.normalizeCategories(ed);
|
||||
B.renderBlocks && B.renderBlocks(ed);
|
||||
}
|
||||
|
||||
ed.__contentLoaded = true;
|
||||
|
||||
} else {
|
||||
B.normalizeCategories && B.normalizeCategories(ed);
|
||||
B.renderBlocks && B.renderBlocks(ed);
|
||||
}
|
||||
|
||||
} catch(e) {
|
||||
log('CORE ERROR', `Nachrichten-Final-Cleanup-Fehler: ${e.message}. Event-Zähler (im Log-Objekt): ${JSON.stringify(eventCounts)}`, 'red', 'error');
|
||||
}
|
||||
});
|
||||
}, 100);
|
||||
|
||||
|
||||
try { var b=ed.Panels.getButton('views','open-blocks'); b && b.set('active',true); } catch {}
|
||||
badgeSay('Inhalt geladen','ok');
|
||||
setTimeout(function(){ badgeSay('bereit'); }, 1200);
|
||||
}
|
||||
}, false);
|
||||
|
||||
try { B.send && B.send('core-ready', { mode: MODE }); } catch {}
|
||||
try { var bd=document.getElementById('badge'); if (bd) bd.remove(); } catch {}
|
||||
});
|
||||
|
||||
window.onerror = function(message, source, lineno, colno, error) {
|
||||
// Diese kritische Funktion MUSS console.error verwenden.
|
||||
console.error(`%c[${PluginName} - GLOBAL ERROR] Uncaught JS Error: ${message} (Quelle: ${source}:${lineno})`, 'color:red; font-weight:bold;');
|
||||
return false;
|
||||
};
|
||||
|
||||
})();
|
||||
624
public/editor/bridge-core.js
Normal file
624
public/editor/bridge-core.js
Normal file
@@ -0,0 +1,624 @@
|
||||
/* /editor/bridge-core.js — Loader + Orchestrator (FINAL & LOG-KONTROLLIERT) */
|
||||
(function () {
|
||||
|
||||
// --- Initialisierung BridgeParts (B) und Plugin-Registry ---
|
||||
if (!window.BridgeParts) window.BridgeParts = {};
|
||||
const B = window.BridgeParts;
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// 🎯 LOKALE LOG-KONFIGURATION & WRAPPER
|
||||
// ----------------------------------------------------------------------
|
||||
const PluginName = 'bridge-core';
|
||||
|
||||
// Setzen Sie dies auf 'false', um alle Logs NUR für dieses Plugin zu deaktivieren.
|
||||
if (B.LOG_CONFIG) {
|
||||
B.LOG_CONFIG.PLUGINS[PluginName] = true; // bridge-core spezifisch deaktivieren (optional)
|
||||
}
|
||||
|
||||
/**
|
||||
* NEUER LOKALER WRAPPER, der die zentrale B.log Funktion verwendet.
|
||||
*/
|
||||
const log = (type, message, color = '#1E90FF', logType = 'info', force = false) => {
|
||||
// Loggt NUR, wenn B.log verfügbar ist (aus general-functions.js).
|
||||
if (typeof B.log === 'function') {
|
||||
B.log(PluginName, `[${type}] ${message}`, color, logType, force);
|
||||
}
|
||||
// Ansonsten wird NICHTS geloggt, bis general-functions.js geladen ist.
|
||||
};
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
// 🛑 GLOBALER LOG ZUR BESTÄTIGUNG DER SKRIPT-AUSFÜHRUNG
|
||||
// log('START', `SKRIPT-AUSFÜHRUNG GESTARTET.`, '#DC143C', 'info', true); // DEAKTIVIERT/IGNORIERT DURCH FEHLENDEN B.log
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// 🛑 KONFIGURATION: NEWSLETTER-PRESET-TOGGLE
|
||||
// ----------------------------------------------------------------------
|
||||
const LOAD_NEWSLETTER_PRESET = false; // <<< KRITISCHER FIX: Auf FALSE gesetzt, um den "defaults" Konflikt zu beheben!
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
if (window.__bridgeCoreInitialized) {
|
||||
log('INIT ABORT', 'Bridge Core wurde bereits initialisiert.', 'orange');
|
||||
return;
|
||||
}
|
||||
window.__bridgeCoreInitialized = true;
|
||||
|
||||
// --- Initialisierung BridgeParts (B) und Plugin-Registry ---
|
||||
B.BASE_PATH_BRIDGE = '../assets/js/bridge/';
|
||||
B.BASE_PATH_CONFIG = B.BASE_PATH_BRIDGE;
|
||||
// NEU: Standard-API-Endpunkt für Fallbacks, falls category-config.js ihn nicht setzt.
|
||||
// **KORREKTUR**: Auf '/api/editor' FIX eingestellt.
|
||||
B.API_BASE = '/api/editor'; // <<< FIX AUF /api/editor
|
||||
B.STORAGE_URL_BASE = '/api/editor'; // <<< FIX: Erzwingt, dass auch der Storage Manager diesen Pfad verwendet
|
||||
|
||||
B.GrapesJSPlugins = [];
|
||||
|
||||
B.registerGrapesJSPlugin = (name, pluginFn) => {
|
||||
B.GrapesJSPlugins.push({ name, pluginFn });
|
||||
log('PLUGIN REGISTER', `Plugin zur Registry hinzugefügt: ${name}`, 'yellow');
|
||||
};
|
||||
|
||||
// --- DEBUG-HELPER UND LOADER-HELPER ---
|
||||
const badgeSay = (text, type = 'info') => {
|
||||
const b=document.getElementById('badge');
|
||||
if (!b) return;
|
||||
b.textContent = text;
|
||||
switch(type) {
|
||||
case 'ok': b.style.background = '#dcfce7'; b.style.color = '#15803d'; b.style.borderColor = '#bbf7d0'; break;
|
||||
case 'error': b.style.background = '#fee2e2'; b.style.color = '#7f1d1d'; b.style.borderColor = '#fecaca'; break;
|
||||
default: b.style.background = '#eef2ff'; b.style.color = '#1e3a8a'; b.style.borderColor = '#c7d2fe';
|
||||
}
|
||||
};
|
||||
|
||||
function loadScript(url, done) {
|
||||
const filename = url.split('/').pop();
|
||||
var s = document.createElement('script');
|
||||
s.src = url + (url.indexOf('?') === -1 ? '?v=' : '&v=') + Date.now();
|
||||
s.async = false;
|
||||
|
||||
s.onload = function(){
|
||||
log('LOAD SUCCESS', `Skript geladen: ${filename}`, 'green');
|
||||
try {
|
||||
done && done();
|
||||
} catch(e){
|
||||
if (e.message.includes('setting getter-only property "defaults"')) {
|
||||
log('RUNTIME WARNING', `IGNORIERE Block-Konflikt in ${filename}: ${e.message}`, 'orange', 'warn');
|
||||
} else {
|
||||
// 🛑 KORREKTUR: force: true explizit auf false setzen (oder weglassen)
|
||||
log('RUNTIME ERROR', `Fehler in Callback nach ${filename}: ${e.message}`, 'red', 'error', false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
s.onerror = function(){
|
||||
// 🛑 KORREKTUR: force: true explizit auf false setzen (oder weglassen)
|
||||
log('LOAD FAILED', `Skript FEHLT oder Pfad falsch: ${filename}`, 'red', 'error', false);
|
||||
badgeSay(`Ladefehler: ${filename}`, 'error');
|
||||
try { done && done(); } catch(e){}
|
||||
};
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* HILFSFUNKTION: Wandelt den Dateinamen (z.B. blocks-standard.js) in den globalen
|
||||
* Objektnamen (z.B. BridgeBlocksStandard) um.
|
||||
*/
|
||||
function getPluginObjectName(fileName) {
|
||||
// 1. Entferne Dateiendung (.js)
|
||||
let name = fileName.replace('.js', ''); // 'blocks-standard'
|
||||
|
||||
// 2. Teile in Bestandteile zerlegen und den ersten Buchstaben groß schreiben
|
||||
const parts = name.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)); // ['Blocks', 'Standard']
|
||||
|
||||
// 3. Mit 'Bridge' prefixen und zusammenfügen
|
||||
return 'Bridge' + parts.join(''); // 'BridgeBlocksStandard'
|
||||
}
|
||||
|
||||
// 🛑 NEUE FUNKTION: Erstellt alle in der Konfiguration definierten Kategorien.
|
||||
function ensureConfiguredCategories(editor) {
|
||||
const bm = editor.BlockManager;
|
||||
// HINWEIS: B.CATEGORY_CONFIG wird in category-config.js befüllt (muss vorher geladen werden)
|
||||
const config = window.BridgeParts?.CATEGORY_CONFIG || {};
|
||||
|
||||
Object.keys(config)
|
||||
.sort((a, b) => (config[a].ord || 999) - (config[b].ord || 999))
|
||||
.forEach(catId => {
|
||||
const catConf = config[catId];
|
||||
// Category wird nur erstellt, wenn sie noch nicht existiert
|
||||
if (!bm.getCategories().get(catId)) {
|
||||
bm.getCategories().add({
|
||||
id: catId,
|
||||
label: catConf.label,
|
||||
open: catConf.open !== false,
|
||||
order: catConf.ord || 999
|
||||
});
|
||||
log('CAT INIT', `Kategorie '${catId}' explizit erstellt.`, 'green');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadBridgeParts(cb){
|
||||
const base = B.BASE_PATH_BRIDGE;
|
||||
|
||||
// 🛑 LOKALES LOGGING ENTFERNT, DA ES VOR B.log LIEGT.
|
||||
// log('LOAD START', 'Starte Laden der modularen Bridge-Teile (Geordnet).');
|
||||
|
||||
const coreFiles = [
|
||||
base + 'general-functions.js', // <<< RE-AKTIVIERT: Für B.log
|
||||
base + 'category-config.js', // <<< RE-AKTIVIERT: Für B.CATEGORY_CONFIG (und damit API-Flexibilität)
|
||||
base + 'library-parts.js',
|
||||
base + 'categorization-master.js',
|
||||
base + 'categorization-cleanup.js',
|
||||
];
|
||||
|
||||
const initialLoadList = [...coreFiles];
|
||||
|
||||
function recursiveLoader(list, index = 0) {
|
||||
if (index >= list.length) {
|
||||
log('LOAD END', 'Initial-Bridge-Skripte geladen.', 'green');
|
||||
|
||||
const config = window.BridgeParts?.CATEGORY_CONFIG || {};
|
||||
let allBlockFiles = [];
|
||||
|
||||
// Dynamisches Sammeln der Block-Dateien aus der Config
|
||||
Object.keys(config)
|
||||
.sort((a, b) => (config[a].ord || 999) - (config[b].ord || 999))
|
||||
.forEach(key => {
|
||||
// Sammelt alle Dateien, egal ob sync oder async
|
||||
if (Array.isArray(config[key].files)) {
|
||||
allBlockFiles.push(...config[key].files.map(file => base + file));
|
||||
}
|
||||
});
|
||||
|
||||
// Duplikate entfernen (falls eine Datei in mehreren Kategorien gelistet ist)
|
||||
allBlockFiles = Array.from(new Set(allBlockFiles));
|
||||
|
||||
function loadBlockFiles(blockIndex = 0) {
|
||||
if (blockIndex >= allBlockFiles.length) {
|
||||
log('LOAD END', 'Alle Blöcke geladen.', 'green');
|
||||
return cb && cb(B);
|
||||
}
|
||||
log('LOADING BLOCKS', `Lade Block-Skript [${blockIndex + 1}/${allBlockFiles.length}]: ${allBlockFiles[blockIndex].split('/').pop()}`);
|
||||
loadScript(allBlockFiles[blockIndex], function(){
|
||||
loadBlockFiles(blockIndex + 1);
|
||||
});
|
||||
}
|
||||
loadBlockFiles();
|
||||
return;
|
||||
}
|
||||
|
||||
// 🛑 LOKALES LOGGING ENTFERNT, DA ES VOR B.log LIEGT.
|
||||
log('LOADING CORE', `Lade Skript [${index + 1}/${initialLoadList.length}]: ${list[index].split('/').pop()}`); // Loggt ab dem 3. Skript, da general-functions.js an 2. Stelle geladen wird.
|
||||
|
||||
loadScript(list[index], function(){
|
||||
recursiveLoader(list, index + 1);
|
||||
});
|
||||
}
|
||||
|
||||
recursiveLoader(initialLoadList);
|
||||
}
|
||||
|
||||
try { parent.postMessage({ source:'bridge', type:'boot' }, '*'); } catch {}
|
||||
var MODE = (window.__editorMode || 'templates').toLowerCase();
|
||||
|
||||
const replaceReferenceLibrary = (editor, ref, mode) => {
|
||||
(window.BridgeParts?.addReferenceLibrary || (()=>{}))(editor, ref, mode);
|
||||
};
|
||||
const upsertCustomForBothCats = (editor, payload) => {
|
||||
(window.BridgeParts?.upsertCustomForBothCats || (()=>{}))(editor, payload);
|
||||
};
|
||||
|
||||
// --- Init & Events (Plugin integriert) ---------------------------------------------
|
||||
loadBridgeParts(function(B){
|
||||
log('INIT START', 'Alle Bridge-Teile geladen, starte GrapesJS-Initialisierung.', 'orange');
|
||||
|
||||
if (typeof grapesjs === 'undefined' || !grapesjs.init) {
|
||||
// 🛑 KORREKTUR: force: true explizit auf false setzen (oder weglassen)
|
||||
log('CRITICAL ERROR', 'Das globale Objekt grapesjs ist nicht verfügbar! Laden von grapes.min.js ist fehlgeschlagen.', 'red', 'error', false);
|
||||
badgeSay('Fehler: GrapesJS nicht geladen!', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// 🛑 KRITISCHER FIX TEIL 1: Registriere alle gesammelten Bridge-Plugins global.
|
||||
if (typeof grapesjs.plugins.add === 'function') {
|
||||
B.GrapesJSPlugins.forEach(p => {
|
||||
grapesjs.plugins.add(p.name, p.pluginFn);
|
||||
log('PLUGIN ACTIVATION', `GrapesJS Plugin global bereitgestellt: ${p.name}`, 'lime');
|
||||
});
|
||||
} else {
|
||||
// 🛑 KORREKTUR: force: true explizit auf false setzen (oder weglassen)
|
||||
log('PLUGIN ERROR', `GrapesJS Plugin-API (grapesjs.plugins.add) fehlt. Plugins können nicht registriert werden.`, 'red', 'error', false);
|
||||
}
|
||||
|
||||
// 🛑 KRITISCHER FIX: Safety Plugin MUSS die fehlenden Views Panels hinzufügen.
|
||||
function safetyPlugin(editor){
|
||||
const pn = editor.Panels, orig = pn.getButton.bind(pn);
|
||||
pn.getButton = (pid, id) => orig(pid, id) || { set(){}, get(){ return null; } };
|
||||
|
||||
// Fügen Sie das Panel 'views' hinzu, wenn es fehlt
|
||||
if(!pn.getPanel('views')) {
|
||||
pn.addPanel({ id: 'views', el: '.gjs-pn-views' });
|
||||
log('PANEL FIX', "Das 'views' Panel wurde nachträglich hinzugefügt.", 'yellow', 'warn');
|
||||
}
|
||||
|
||||
// Stellen Sie sicher, dass der Block Manager in den Views-Container rendert
|
||||
editor.Config.blockManager = editor.Config.blockManager || {};
|
||||
editor.Config.blockManager.appendTo = editor.Config.blockManager.appendTo || '.gjs-blocks';
|
||||
|
||||
// Der fehlerhafte Timeout-Block wurde entfernt.
|
||||
}
|
||||
|
||||
let pluginsList = [
|
||||
safetyPlugin,
|
||||
// 🛑 KRITISCHE ERGÄNZUNG: Aktiviert das registrierte API-Plugin
|
||||
'bridge-blocks-api',
|
||||
'bridge-categorization-master',
|
||||
'bridge-categorization-cleanup',
|
||||
];
|
||||
|
||||
if (LOAD_NEWSLETTER_PRESET) {
|
||||
pluginsList.push('gjs-preset-newsletter');
|
||||
}
|
||||
|
||||
// Speicherkonfiguration extrahieren, um die URL in onLoad zu verwenden.
|
||||
// 🎯 KORREKTUR für mehr Flexibilität: Verwende B.STORAGE_URL_BASE, falls gesetzt, anstatt window.location.href.
|
||||
// Verwenden Sie B.API_BASE (Standard /api/editor) als Fallback für die Storage-URL
|
||||
const storageBase = B.STORAGE_URL_BASE || B.API_BASE; // B.API_BASE sollte jetzt korrekt sein
|
||||
|
||||
// Robustes Anhängen von Query-Parametern.
|
||||
// Prüft, ob 'storageBase' bereits Query-Parameter enthält ('?')
|
||||
const actionSeparator = storageBase.indexOf('?') === -1 ? '?' : '&';
|
||||
|
||||
const loadUrl = storageBase + actionSeparator + 'action=get&resource=' + (window.__editorMode || 'templates') + '&id=' + (window.__editorId || 0); // KRITISCHE ERGÄNZUNG: Resource und ID
|
||||
const storeUrl = storageBase + actionSeparator + 'action=update&resource=' + (window.__editorMode || 'templates') + '&id=' + (window.__editorId || 0); // KRITISCHE ERGÄNZUNG: Resource und ID
|
||||
|
||||
const storageConf = {
|
||||
type: 'remote',
|
||||
// urlLoad: loadUrl, // ENTFERNT (korrekt, da customFetch verwendet wird)
|
||||
urlStore: storeUrl,
|
||||
|
||||
// 🛑 KRITISCHER ABSCHNITT: customFetch MUSS DIE ERWARTETE SIGNATUR HABEN: customFetch(url, options)
|
||||
customFetch: async (url, options) => { // <<< KORREKTUR DER SIGNATUR
|
||||
// 1. Log Start
|
||||
log('STORAGE START', 'Template wird geladen.', '#008080', 'info', true);
|
||||
// 2. Log Link
|
||||
log('API REQUEST', `Link für den API Request: ${loadUrl}`, '#4682B4', 'log', false);
|
||||
|
||||
const fetchOptions = {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
// Wichtig: Die übergebenen Optionen nicht vergessen zu mergen
|
||||
...options
|
||||
};
|
||||
|
||||
let data = {};
|
||||
let rawResponse = '';
|
||||
|
||||
try {
|
||||
// Verwendung der intern definierten loadUrl
|
||||
const response = await fetch(loadUrl, fetchOptions);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`HTTP-Fehler ${response.status}: ${errorText}`);
|
||||
}
|
||||
|
||||
// Holen des Raw Texts, um ihn loggen und parsen zu können
|
||||
rawResponse = await response.text();
|
||||
|
||||
// 3. Log Result
|
||||
log('API RESPONSE', 'Result vom API Request (Raw Text/JSON):', '#4682B4', 'log', false);
|
||||
console.log(rawResponse); // Loggt den reinen String für die Analyse
|
||||
|
||||
// Versuch der JSON-Analyse (um den GrapesJS-Fehler zu vermeiden)
|
||||
try {
|
||||
data = JSON.parse(rawResponse);
|
||||
log('STORAGE PARSE', 'Raw Response als JSON geparst.', 'green');
|
||||
} catch (e) {
|
||||
log('STORAGE PARSE ERROR', `Fehler beim Parsen der Antwort: ${e.message}. Antwort war wahrscheinlich kein gültiges JSON.`, 'red', 'error', true);
|
||||
// Im Falle eines Parsing-Fehlers, leeres Objekt für Fallback-Logik
|
||||
data = {};
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
log('STORAGE FETCH ERROR', `Fehler beim Abruf: ${e.message}`, 'red', 'error', true);
|
||||
// Sicherstellen, dass die Promise mit einem leeren Zustand erfüllt wird
|
||||
// Wir müssen dennoch den Log End ausführen, bevor wir zurückkehren
|
||||
}
|
||||
|
||||
// 4. Log End
|
||||
log('STORAGE END', 'Template wurde geladen.', '#008080', 'info', true);
|
||||
|
||||
// --- Logik zur Extraktion des GrapesJS States aus der API-Antwort ---
|
||||
let state = {};
|
||||
|
||||
if (data && data.gjs_data) {
|
||||
log('STORAGE LOAD', 'Voller GrapesJS State aus "gjs_data" geladen.', 'green');
|
||||
state = data.gjs_data;
|
||||
}
|
||||
else if (data && data.content) {
|
||||
try {
|
||||
const parsedState = JSON.parse(data.content);
|
||||
log('STORAGE LOAD', 'Voller GrapesJS State aus "content" (JSON-String) geladen.', 'yellow');
|
||||
state = parsedState;
|
||||
} catch (e) {
|
||||
log('STORAGE ERROR', `Fehler beim Parsen von "content": ${e.message}.`, 'red', 'error');
|
||||
}
|
||||
}
|
||||
// HINWEIS: Füge Fallback für "topContent" hinzu, basierend auf dem Server-Log
|
||||
else if (data && data.topContent) {
|
||||
try {
|
||||
const parsedState = JSON.parse(data.topContent);
|
||||
log('STORAGE LOAD', 'Voller GrapesJS State aus "topContent" (JSON-String) geladen.', 'green');
|
||||
state = parsedState;
|
||||
} catch (e) {
|
||||
log('STORAGE ERROR', `Fehler beim Parsen von "topContent": ${e.message}.`, 'red', 'error');
|
||||
}
|
||||
}
|
||||
else {
|
||||
log('STORAGE WARNING', 'Kein vollständiger GrapesJS State gefunden. Editor lädt leeren State.', 'orange', 'warn');
|
||||
}
|
||||
|
||||
// customFetch MUSS den geladenen State zurückgeben
|
||||
return state;
|
||||
},
|
||||
// --- ENDE customFetch ---
|
||||
|
||||
// onLoad ist bei customFetch nicht mehr nötig
|
||||
// onLoad: (response) => { ... },
|
||||
|
||||
// KRITISCH: Speichert den vollen State als JSON-String im Feld 'json_content'.
|
||||
onStore: (data) => {
|
||||
// ACHTUNG: ed existiert hier nicht, muss über window.__gjs geladen werden ODER ed als Argument akzeptiert werden
|
||||
const ed = window.__gjs;
|
||||
return {
|
||||
json_content: JSON.stringify(data),
|
||||
html: ed ? ed.getHtml() : '' // Fügen Sie den HTML-Output zur Abwärtskompatibilität hinzu
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
var ed = grapesjs.init({
|
||||
container: '#gjs',
|
||||
height: '100vh',
|
||||
|
||||
// 🛑 KRITISCHE KORREKTUR: storageManager aktivieren und konfigurieren
|
||||
storageManager: storageConf,
|
||||
|
||||
plugins: pluginsList,
|
||||
pluginsOpts: {},
|
||||
// 🛑 KRITISCHE ERGÄNZUNG: Verhindert das automatische Ausblenden leerer Kategorien
|
||||
blockManager: {
|
||||
hideEmpty: false
|
||||
}
|
||||
});
|
||||
|
||||
window.__gjs = ed;
|
||||
|
||||
// 🛑 KRITISCHE KORREKTUR 1: Explizite Erstellung aller konfigurierten Kategorien
|
||||
ensureConfiguredCategories(ed);
|
||||
|
||||
// 🛑 KRITISCHE KORREKTUR 2: Sofortige Label-Korrektur
|
||||
// Überschreibt den potenziell falschen, durch GrapesJS gesetzten Label-Namen
|
||||
Object.keys(B.CATEGORY_CONFIG || {}).forEach(catId => {
|
||||
const expectedLabel = B.CATEGORY_CONFIG[catId].label;
|
||||
const categoryModel = ed.BlockManager.getCategories().get(catId);
|
||||
|
||||
if (categoryModel && categoryModel.get('label') !== expectedLabel) {
|
||||
// Setzen ohne das 'change:label' Event auszulösen (optional, aber sauber)
|
||||
categoryModel.set('label', expectedLabel, { silent: true });
|
||||
log('LABEL FIX', `Kategorie '${catId}' Label auf korrigiert: '${expectedLabel}'`, 'yellow', 'warn');
|
||||
}
|
||||
});
|
||||
// ---------------------------------------------------
|
||||
|
||||
B.ensureViews && B.ensureViews(ed);
|
||||
|
||||
log('BLOCK REGISTER', 'Registriere Bridge Blöcke, um Preset-Defaults zu überschreiben.', 'purple');
|
||||
|
||||
// 🛑 DYNAMISCHE AKTIVIERUNG DER SYNCHRONEN BLÖCKE (Ersetzt die fixen Aufrufe)
|
||||
if (B.CATEGORY_CONFIG && ed) {
|
||||
log('DYNAMIC ACTIVATION', 'Starte Aktivierung synchroner Block-Plugins (via Config).', 'purple');
|
||||
|
||||
// Iteriere über die konfigurierten Kategorien
|
||||
Object.keys(B.CATEGORY_CONFIG).forEach(catId => {
|
||||
const config = B.CATEGORY_CONFIG[catId];
|
||||
|
||||
// Verarbeite nur SYNCHRONE Plugins, die Dateien angeben
|
||||
if (config.registration_mode === 'sync' && Array.isArray(config.files)) {
|
||||
|
||||
config.files.forEach(fileName => {
|
||||
|
||||
// Korrigierte Funktion liefert jetzt z.B. 'BridgeBlocksCustom'
|
||||
const objectName = getPluginObjectName(fileName);
|
||||
const plugin = window[objectName];
|
||||
|
||||
// Prüfen, ob das Skript geladen wurde und die Register-Funktion vorhanden ist
|
||||
if (plugin && typeof plugin.register === 'function') {
|
||||
log('DYNAMIC ACTIVATION', `Registriere sync Plugin: ${objectName} (${fileName})`, 'lime');
|
||||
try {
|
||||
plugin.register(ed);
|
||||
} catch(e) {
|
||||
log('DYNAMIC ACTIVATION ERROR', `Fehler beim Registrieren von ${objectName}: ${e.message}`, 'red', 'error');
|
||||
}
|
||||
} else {
|
||||
log('DYNAMIC ACTIVATION WARNING', `Sync Plugin Objekt oder .register() Methode nicht gefunden: ${objectName} (${fileName})`, 'orange', 'warn');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
// ---------------------------------------------------
|
||||
|
||||
log('INIT API', 'API-Elemente werden nun durch das Plugin bridge-blocks-api geladen. (ASYNCHRON)', 'orange');
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// DEBUGGING: ZÄHLE REKURSIVE EVENTS
|
||||
// ----------------------------------------------------------------------
|
||||
let eventCounts = {};
|
||||
let isParsing = false;
|
||||
const MAX_CALLS = 1000;
|
||||
|
||||
const debugEvents = [
|
||||
'component:add',
|
||||
'component:update',
|
||||
'change:components',
|
||||
'block:add',
|
||||
'change:attributes',
|
||||
'comp:update:status'
|
||||
];
|
||||
|
||||
const debugListener = (event, model) => {
|
||||
if (!isParsing) return;
|
||||
|
||||
if (!eventCounts[event]) {
|
||||
eventCounts[event] = 0;
|
||||
}
|
||||
eventCounts[event]++;
|
||||
|
||||
if (eventCounts[event] === MAX_CALLS + 1) {
|
||||
// Diese kritischen Debug-Meldungen bleiben DIREKT im console-Objekt,
|
||||
// da sie immer sichtbar sein müssen, um Endlosschleifen zu erkennen.
|
||||
console.error(`%c[DEBUG RECURSION ALARM] 🚨 Event '${event}' hat den Grenzwert von ${MAX_CALLS} überschritten!`, 'color:red; font-size: 1.1em; font-weight: bold;');
|
||||
}
|
||||
|
||||
if (eventCounts[event] > MAX_CALLS && eventCounts[event] < (MAX_CALLS + 10)) {
|
||||
const type = (model && typeof model.get === 'function') ? model.get('type') : 'N/A';
|
||||
const parentType = (model && typeof model.parent === 'function' && model.parent()) ? model.parent().get('type') : 'N/A';
|
||||
// Diese bleiben console.log aus demselben Grund
|
||||
console.log(`%c [RECURSION SOURCE] Event: ${event}, Type: ${type}, Parent: ${parentType}`, 'color: #8b0000;');
|
||||
}
|
||||
};
|
||||
|
||||
ed.on('load', function() {
|
||||
debugEvents.forEach(event => ed.on(event, (model) => debugListener(event, model)));
|
||||
|
||||
setTimeout(() => {
|
||||
(B.waitForBlocks ? B.waitForBlocks(ed) : Promise.resolve()).then(function(){
|
||||
try {
|
||||
log('CORE WARN', 'Führe finalen, verzögerten Cleanup-Lauf durch (2000ms).', 'orange', 'warn');
|
||||
|
||||
B.normalizeCategories && B.normalizeCategories(ed);
|
||||
B.renderBlocks && B.renderBlocks(ed);
|
||||
|
||||
} catch(e) {
|
||||
log('CORE ERROR', `Finaler Cleanup-Fehler: ${e.message}`, 'red', 'error');
|
||||
}
|
||||
});
|
||||
}, 2000);
|
||||
|
||||
}, { once: true });
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// MESSAGE HANDLER
|
||||
// ----------------------------------------------------------------------
|
||||
window.addEventListener('message', async function(ev){
|
||||
var data = ev.data || {};
|
||||
if (data.source !== 'admin') return;
|
||||
|
||||
if (data.type === 'init'){
|
||||
B.ensureViews && B.ensureViews(ed);
|
||||
|
||||
var html = (data.html || '').trim();
|
||||
if (!html) html = '<table style="width:100%;font-family:Arial,sans-serif"><tr><td><h1>Neues Dokument</h1><p>Inhalt ...</p></td></tr></table>';
|
||||
|
||||
const applySnips = function(arr){
|
||||
const list = (Array.isArray(arr)?arr:[]).map(s => ({ id:s.id, name:s.name, html: s.html || s.content || '' }));
|
||||
|
||||
B.replaceSnippetBlocks && B.replaceSnippetBlocks(ed, list);
|
||||
|
||||
upsertCustomForBothCats(ed, {
|
||||
ref: (data.ref && (Array.isArray(data.ref.sections) || Array.isArray(data.ref.blocks))) ? {
|
||||
sections: data.ref.sections || [],
|
||||
blocks: data.ref.blocks || []
|
||||
} : { sections: [], blocks: [] },
|
||||
snippets: list
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
try {
|
||||
// Erneutes Normalisieren nach Laden der Snippets (falls nötig)
|
||||
B.normalizeCategories && B.normalizeCategories(ed);
|
||||
B.ensureViews && B.ensureViews(ed);
|
||||
B.renderBlocks && B.renderBlocks(ed);
|
||||
log('CORE WARN', 'normalize/render nach applySnips ausgeführt (1ms).', 'orange', 'warn');
|
||||
} catch(e) {
|
||||
log('CORE ERROR', `applySnips-Cleanup-Fehler: ${e.message}`, 'red', 'error');
|
||||
}
|
||||
}, 1);
|
||||
|
||||
};
|
||||
|
||||
if (Array.isArray(data.snippets) && data.snippets.length) applySnips(data.snippets);
|
||||
else (B.fetchSnippets ? B.fetchSnippets() : Promise.resolve([])).then(applySnips);
|
||||
|
||||
if (data.ref && (Array.isArray(data.ref.sections) || Array.isArray(data.ref.blocks))) {
|
||||
replaceReferenceLibrary(ed, {
|
||||
sections: data.ref.sections || [],
|
||||
blocks: data.ref.blocks || []
|
||||
}, MODE);
|
||||
}
|
||||
|
||||
// Finaler Aufruf nachrichtengesteuert (konsolidiert)
|
||||
setTimeout(() => {
|
||||
(B.waitForBlocks ? B.waitForBlocks(ed) : Promise.resolve()).then(function(){
|
||||
try {
|
||||
log('CORE WARN', 'Führe nachrichtengesteuerten Final-Cleanup-Lauf durch (100ms).', 'orange', 'warn');
|
||||
|
||||
// 🛑 KRITISCHE KORREKTUR: Entferne das erzwungene ed.setComponents(html)
|
||||
// Das Laden des Inhalts wird jetzt vom storageManager übernommen (via customFetch).
|
||||
if (!ed.__contentLoaded) {
|
||||
log('CONTENT', 'Erster Ladevorgang (storageManager) ist abgeschlossen.', 'orange');
|
||||
|
||||
// HINWEIS: Wenn der Editor initial leer lädt (z.B. neue Vorlage),
|
||||
// MUSS hier der initiale HTML-Code eingefügt werden.
|
||||
// Da der storageManager aber automatisch lädt,
|
||||
// sollte dieser Block nur für den Initialfall "Neu" greifen.
|
||||
if (html && !ed.getComponents().length) {
|
||||
window.__GJS_IS_PARSING = true;
|
||||
isParsing = true;
|
||||
eventCounts = {};
|
||||
try {
|
||||
ed.setComponents(html);
|
||||
} catch (e) {
|
||||
log('SET COMPONENTS FAILED', `setComponents Fehler: ${e.message}. Aufgerufene Event-Zähler: ${JSON.stringify(eventCounts)}`, 'red', 'error');
|
||||
throw e;
|
||||
} finally {
|
||||
window.__GJS_IS_PARSING = false;
|
||||
isParsing = false;
|
||||
log('CONTENT', 'HTML-Inhalt in den Editor geladen (FALLBACK).', 'orange');
|
||||
}
|
||||
}
|
||||
|
||||
ed.__contentLoaded = true;
|
||||
|
||||
}
|
||||
|
||||
// Normalisierung am Ende
|
||||
B.normalizeCategories && B.normalizeCategories(ed);
|
||||
B.renderBlocks && B.renderBlocks(ed);
|
||||
|
||||
} catch(e) {
|
||||
log('CORE ERROR', `Nachrichten-Final-Cleanup-Fehler: ${e.message}. Event-Zähler (im Log-Objekt): ${JSON.stringify(eventCounts)}`, 'red', 'error');
|
||||
}
|
||||
});
|
||||
}, 100);
|
||||
|
||||
|
||||
try { var b=ed.Panels.getButton('views','open-blocks'); b && b.set('active',true); } catch {}
|
||||
badgeSay('Inhalt geladen','ok');
|
||||
setTimeout(function(){ badgeSay('bereit'); }, 1200);
|
||||
}
|
||||
}, false);
|
||||
|
||||
try { B.send && B.send('core-ready', { mode: MODE }); } catch {}
|
||||
try { var bd=document.getElementById('badge'); if (bd) bd.remove(); } catch {}
|
||||
});
|
||||
|
||||
window.onerror = function(message, source, lineno, colno, error) {
|
||||
// Diese kritische Funktion MUSS console.error verwenden.
|
||||
console.error(`%c[${PluginName} - GLOBAL ERROR] Uncaught JS Error: ${message} (Quelle: ${source}:${lineno})`, 'color:red; font-weight:bold;');
|
||||
return false;
|
||||
};
|
||||
|
||||
})();
|
||||
76
public/editor/config.js
Normal file
76
public/editor/config.js
Normal file
@@ -0,0 +1,76 @@
|
||||
/* /editor/config.js (SCHRITT 35: LOG-EBENEN-KONTROLLE) */
|
||||
(function() {
|
||||
|
||||
// Stelle sicher, dass BridgeParts existiert und hole die registrierten Plugins.
|
||||
const B = window.BridgeParts || {};
|
||||
|
||||
// --- 🎯 ZENTRALE LOG-KONFIGURATION (Überschreibt general-functions.js Defaults) ---
|
||||
// HINWEIS: Dies muss NACH general-functions.js geladen werden.
|
||||
B.LOG_CONFIG = B.LOG_CONFIG || {};
|
||||
|
||||
// 1. HAUPTSCHALTER: Deaktiviert alle normalen Logs (muss auf 'true' sein, damit die Ebenen-Schalter wirken)
|
||||
B.LOG_CONFIG.GLOBAL_DEBUG = true;
|
||||
|
||||
// 2. EBENEN-SCHALTER (wirken nur, wenn GLOBAL_DEBUG = true):
|
||||
B.LOG_CONFIG.INFO_ENABLED = true; // Aktiviert/Deaktiviert alle Info-Logs (B.log mit type='info')
|
||||
B.LOG_CONFIG.WARN_ENABLED = true; // Aktiviert/Deaktiviert alle Warn-Logs (B.log mit type='warn')
|
||||
B.LOG_CONFIG.ERROR_ENABLED = true; // Aktiviert/Deaktiviert alle Error-Logs (B.log mit type='error')
|
||||
|
||||
// 3. DATEN-SCHALTER: Aktiviert/Deaktiviert die Ausgabe großer Array-Daten (B.logData)
|
||||
B.LOG_CONFIG.DATA_ENABLED = true;
|
||||
|
||||
// ----------------------------------------------------------------------------------
|
||||
|
||||
|
||||
// Sammle alle dynamisch registrierten Plugin-Namen.
|
||||
// Der Array B.GrapesJSPlugins wurde von bridge-core.js, blocks-api.js etc. gefüllt.
|
||||
const dynamicPluginNames = B.GrapesJSPlugins
|
||||
? B.GrapesJSPlugins.map(p => p.name)
|
||||
: [];
|
||||
|
||||
// Optional: Fügen Sie statische GrapesJS-Plugins hinzu
|
||||
const staticPlugins = [
|
||||
'gjs-preset-newsletter' // Beispiel: Fügt den Newsletter-Preset hinzu, falls gewünscht
|
||||
];
|
||||
|
||||
// Kombiniere alle Plugin-Namen zu einer eindeutigen Liste
|
||||
const uniquePlugins = [...new Set([
|
||||
...dynamicPluginNames,
|
||||
...staticPlugins
|
||||
])];
|
||||
|
||||
|
||||
// Definiere die Haupt-Konfiguration für GrapesJS
|
||||
const editorConfig = {
|
||||
// 1. WICHTIG: Ersetze 'gjs' durch die ID des Containers
|
||||
container: '#gjs',
|
||||
|
||||
// 2. KRITISCH: Die dynamisch erstellte Plugin-Liste
|
||||
plugins: uniquePlugins,
|
||||
|
||||
// 3. Plugin-Optionen (können leer bleiben, wenn keine Optionen benötigt werden)
|
||||
pluginsOpts: {
|
||||
// Hier Optionen für einzelne Plugins eintragen
|
||||
},
|
||||
|
||||
// --- Andere Basis-Konfigurationen ---
|
||||
panels: {
|
||||
defaults: [
|
||||
{ id: 'options', el: '.panel__options', buttons: [{ id: 'save', label: 'Speichern', className: 'fa fa-floppy-o' }] },
|
||||
{ id: 'views', el: '.panel__views' },
|
||||
]
|
||||
},
|
||||
|
||||
// ... Fügen Sie hier weitere GrapesJS-Optionen ein (z.B. device buttons)
|
||||
};
|
||||
|
||||
// Starte GrapesJS
|
||||
// window.GrapesJS.init wurde in bridge-core.js definiert, um GrapesJS zu starten.
|
||||
if (window.GrapesJS && window.GrapesJS.init) {
|
||||
// Übergebe editorConfig und die Liste der Plugin-Funktionen
|
||||
window.GrapesJS.init(editorConfig, B.GrapesJSPlugins.map(p => p.name), B.GrapesJSPlugins);
|
||||
} else {
|
||||
console.error('GrapesJS.init ist in window.GrapesJS nicht verfügbar. Wurde bridge-core.js geladen?');
|
||||
}
|
||||
|
||||
})();
|
||||
64
public/editor/editor-core (Kopie).php
Normal file
64
public/editor/editor-core (Kopie).php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
$mode = strtolower($_GET['mode'] ?? 'templates');
|
||||
$ts = time();
|
||||
?><!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Editor</title>
|
||||
<link rel="stylesheet" href="../vendor/grapesjs/grapes.min.css" />
|
||||
<style>
|
||||
html,body{height:100%}body{margin:0;background:#f8fafc;color:#0f172a}#gjs{height:100vh}
|
||||
|
||||
.gjs-one-bg{background-color:#fff!important}.gjs-two-color{color:#0f172a!important}
|
||||
.gjs-three-bg{background-color:#f8fafc!important}.gjs-four-color{color:#334155!important}
|
||||
#badge{position:fixed;right:8px;top:8px;background:#eef2ff;color:#1e3a8a;border:1px solid #c7d2fe;border-radius:999px;padding:4px 10px;font:12px system-ui;z-index:2147483647;opacity:.9}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="badge">lädt …</div>
|
||||
<div id="gjs"></div>
|
||||
|
||||
<div id="blocks"></div>
|
||||
|
||||
<script>
|
||||
function logToParent(type, detail){ try{ parent.postMessage({source:'editor-core',type:type,detail:String(detail||'')},'*'); }catch(e){} }
|
||||
window.addEventListener('error', function(e){
|
||||
var b=document.getElementById('badge');
|
||||
if(b){ b.textContent='Fehler: '+(e&&e.message?e.message:'unbekannt'); b.style.background='#fee2e2'; b.style.color='#7f1d1d'; b.style.borderColor='#fecaca'; }
|
||||
logToParent('window-error', e && e.message ? e.message : 'unknown');
|
||||
});
|
||||
function loadLocalScript(src, onok){
|
||||
var s=document.createElement('script'); s.src=src; s.async=false;
|
||||
s.onload=function(){ logToParent('script-ok', src); onok&&onok(); };
|
||||
s.onerror=function(){ var b=document.getElementById('badge'); if(b){ b.textContent='Fehlt: '+src; b.style.background='#fee2e2'; b.style.color='#7f1d1d'; b.style.borderColor='#fecaca'; } logToParent('script-missing', src); };
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
|
||||
logToParent('boot','start');
|
||||
|
||||
// 1) GrapesJS laden
|
||||
loadLocalScript('../vendor/grapesjs/grapes.min.js?v=<?=$ts?>', function(){
|
||||
if(typeof window.grapesjs==='undefined'){ document.getElementById('badge').textContent='grapesjs nicht verfügbar'; logToParent('gjs-missing','window.grapesjs undefined'); return; }
|
||||
|
||||
// 2) Plugin laden
|
||||
loadLocalScript('../vendor/grapesjs-preset-newsletter/grapesjs-preset-newsletter.min.js?v=<?=$ts?>', function(){
|
||||
|
||||
// 3) BRIDGE ZUERST (mit Cache-Bust) – meldet sich sofort mit bridge:boot
|
||||
loadLocalScript('bridge-core.js?v=<?=$ts?>', function(){
|
||||
// 4) Danach config.js (Bibliothek)
|
||||
// loadLocalScript('config.js?v=<?=$ts?>');
|
||||
});
|
||||
|
||||
// Heartbeat vom Core (sichtbar im Hauptfenster)
|
||||
var hb=0, timer=setInterval(function(){ hb++; if(hb>60){clearInterval(timer);return;} logToParent('hb','tick '+hb); }, 200);
|
||||
|
||||
// Mode für die Bridge bereitstellen
|
||||
window.__editorMode = "<?=htmlspecialchars($mode,ENT_QUOTES)?>";
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
79
public/editor/editor-core.php
Normal file
79
public/editor/editor-core.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
$mode = strtolower($_GET['mode'] ?? 'templates');
|
||||
$ts = time();
|
||||
?><!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Editor</title>
|
||||
<link rel="stylesheet" href="../vendor/grapesjs/grapes.min.css" />
|
||||
<style>
|
||||
html,body{height:100%}body{margin:0;background:#f8fafc;color:#0f172a}#gjs{height:100vh}
|
||||
|
||||
.gjs-one-bg{background-color:#fff!important}.gjs-two-color{color:#0f172a!important}
|
||||
.gjs-three-bg{background-color:#f8fafc!important}.gjs-four-color{color:#334155!important}
|
||||
#badge{position:fixed;right:8px;top:8px;background:#eef2ff;color:#1e3a8a;border:1px solid #c7d2fe;border-radius:999px;padding:4px 10px;font:12px system-ui;z-index:2147483647;opacity:.9}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="badge">lädt …</div>
|
||||
<div id="gjs"></div>
|
||||
|
||||
<div id="blocks"></div>
|
||||
|
||||
<script>
|
||||
function logToParent(type, detail){ try{ parent.postMessage({source:'editor-core',type:type,detail:String(detail||'')},'*'); }catch(e){} }
|
||||
window.addEventListener('error', function(e){
|
||||
var b=document.getElementById('badge');
|
||||
if(b){ b.textContent='Fehler: '+(e&&e.message?e.message:'unbekannt'); b.style.background='#fee2e2'; b.style.color='#7f1d1d'; b.style.borderColor='#fecaca'; }
|
||||
logToParent('window-error', e && e.message ? e.message : 'unknown');
|
||||
});
|
||||
function loadLocalScript(src, onok){
|
||||
// Hinzufügen des Cache-Bust-Parameters zur URL
|
||||
// Die Variable $ts wird durch PHP im HTML-Kontext eingefügt
|
||||
const url = src + (src.indexOf('?') === -1 ? '?v=' : '&v=') + <?=$ts?>;
|
||||
var s=document.createElement('script'); s.src=url; s.async=false;
|
||||
s.onload=function(){ logToParent('script-ok', src); onok&&onok(); };
|
||||
s.onerror=function(){ var b=document.getElementById('badge'); if(b){ b.textContent='Fehlt: '+src; b.style.background='#fee2e2'; b.style.color='#7f1d1d'; b.style.borderColor='#fecaca'; } logToParent('script-missing', src); };
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
|
||||
logToParent('boot','start');
|
||||
|
||||
// 1) GrapesJS laden
|
||||
loadLocalScript('../vendor/grapesjs/grapes.min.js', function(){
|
||||
if(typeof window.grapesjs==='undefined'){
|
||||
document.getElementById('badge').textContent='grapesjs nicht verfügbar';
|
||||
logToParent('gjs-missing','window.grapesjs undefined');
|
||||
return;
|
||||
}
|
||||
|
||||
// 2.A) KRITISCHE HELPER ZUERST LADEN (Category Config)
|
||||
loadLocalScript('../assets/js/bridge/category-config.js', function() {
|
||||
|
||||
// 2.B) Dann die zentrale Log-Funktion
|
||||
// Diese muss geladen sein, bevor bridge-core.js startet!
|
||||
loadLocalScript('../assets/js/bridge/general-functions.js', function() {
|
||||
|
||||
// 3) Plugin laden (GrapesJS Preset Newsletter)
|
||||
loadLocalScript('../vendor/grapesjs-preset-newsletter/grapesjs-preset-newsletter.min.js', function(){
|
||||
|
||||
// 4) BRIDGE-CORE laden – jetzt kann es B.LOG_CONFIG auf false setzen!
|
||||
loadLocalScript('bridge-core.js', function(){
|
||||
// 5) Danach config.js (Bibliothek)
|
||||
// loadLocalScript('config.js');
|
||||
});
|
||||
|
||||
// Heartbeat vom Core (sichtbar im Hauptfenster)
|
||||
var hb=0, timer=setInterval(function(){ hb++; if(hb>60){clearInterval(timer);return;} logToParent('hb','tick '+hb); }, 200);
|
||||
|
||||
// Mode für die Bridge bereitstellen
|
||||
window.__editorMode = "<?=htmlspecialchars($mode,ENT_QUOTES)?>";
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user