This commit is contained in:
2026-01-17 04:05:22 +01:00
parent 4e9e9a6ef0
commit 3752a531f1
3 changed files with 77 additions and 7 deletions

View File

@@ -1,5 +1,11 @@
# Mailadmin Email Template System (R17 modular)
Stand: 2025-08-31
Stand: 2026-01-17
## Kurzuebersicht
- Admin UI fuer Templates, Sections und Snippets
- GrapesJS Editor lokal eingebunden (kein CDN)
- Modularer Bridge-Core fuer Blöcke, Kategorien und Editor-Erweiterungen
- JSON API mit Dual-DB und Prefix-Support
## Struktur
mailadmin/
@@ -23,8 +29,12 @@ mailadmin/
ui-editor.js # Editor-Dialog + Handshake
editor/
editor-core.php # LÄDT NUR LOKAL (kein CDN)
bridge-core.js
bridge-core.js # Loader + Guards + Editor-Orchestrierung
config.js # Bibliothek in Block-Leiste
public/assets/js/bridge/
rte-editor.js # RichText-Editor (Modal)
table-builder.js # Table Builder (Modal)
blocks-*.js # Block-Definitionen
## Vendor (lokal bereitstellen)
Lege die GrapesJS-Dateien lokal ab (kein CDN):
@@ -36,10 +46,16 @@ Lege die GrapesJS-Dateien lokal ab (kein CDN):
Nutze `schema.sql` (inkl. *NULLfähiger* Fremdschlüssel & ON DELETE SET NULL).
Prefix standardmäßig: `emailtemplate_`.
## Editor-Flags (window.BridgeParts)
- ENABLE_EDITOR_EXTENSIONS
- ENABLE_EDITOR_BEHAVIOR
- ENABLE_PLACEHOLDERS
- ENABLE_TABLE_BUILDER
- ENABLE_RTE
## Schnellstart
1) `mailadmin/inc/config.php` anlegen (siehe `config.example.php`).
2) `schema.sql` in deiner Templates-Datenbank ausführen.
3) Vendor-Dateien in `public/vendor/...` kopieren.
4) `public/tools/config-doctor.php` & `public/api.php?action=health` prüfen.
5) `public/index.php` öffnen → „Neu …“, „Vorschau“, „Im EMailEditor öffnen“ etc.

View File

@@ -1 +1,3 @@
This archive contains the R17 modular Email Template System. Fill inc/config.php and vendor libs.
Mailadmin ist ein modulares Email-Template-System mit Admin-UI und lokal eingebundenem GrapesJS-Editor.
Es verwaltet Templates, Sections und Snippets, nutzt eine JSON-API mit Prefix-Support
und laesst sich ueber Bridge-Module (Blocks, RTE, Table Builder) erweitern.

View File

@@ -32,13 +32,17 @@
window.__bridgeModalGuardsInstalled = true;
window.__bridgeModalAllowClose = false;
const allowCloseOnce = () => {
window.__bridgeModalAllowClose = true;
setTimeout(() => { window.__bridgeModalAllowClose = false; }, 0);
};
document.addEventListener('click', (evt) => {
const closeHit = evt.target && evt.target.closest
? evt.target.closest('.gjs-mdl-btn-close,[data-bridge-modal-close="1"]')
: null;
if (closeHit) {
window.__bridgeModalAllowClose = true;
setTimeout(() => { window.__bridgeModalAllowClose = false; }, 0);
allowCloseOnce();
}
const container = evt.target && evt.target.closest
? evt.target.closest('.gjs-mdl-container')
@@ -53,6 +57,15 @@
}
}, true);
document.addEventListener('mousedown', (evt) => {
const closeHit = evt.target && evt.target.closest
? evt.target.closest('.gjs-mdl-btn-close,[data-bridge-modal-close="1"]')
: null;
if (closeHit) {
allowCloseOnce();
}
}, true);
document.addEventListener('keydown', (evt) => {
if (evt.key !== 'Escape') return;
const hasModal = document.querySelector('.gjs-mdl-container');
@@ -603,9 +616,9 @@
try {
const modalEl = ed.Modal && ed.Modal.el;
if (!modalEl) return;
if (modalEl.querySelector('[data-bridge-viewcode-close]')) return;
const contentEl = modalEl.querySelector('.gjs-mdl-content');
if (!contentEl) return;
if (modalEl.querySelector('[data-bridge-viewcode-close]')) return;
const footer = document.createElement('div');
footer.style.display = 'flex';
footer.style.justifyContent = 'flex-end';
@@ -629,6 +642,45 @@
} catch {}
});
}
if (ed && ed.Modal && ed.Modal.getModel && !ed.Modal.__bridgeModelHooked) {
ed.Modal.__bridgeModelHooked = true;
const mdl = ed.Modal.getModel();
if (mdl && typeof mdl.on === 'function') {
mdl.on('change:open', () => {
if (!mdl.get('open')) return;
setTimeout(() => {
try {
const modalEl = ed.Modal && ed.Modal.el;
if (!modalEl) return;
const contentEl = modalEl.querySelector('.gjs-mdl-content');
if (!contentEl) return;
if (modalEl.querySelector('[data-bridge-viewcode-close]')) return;
const footer = document.createElement('div');
footer.style.display = 'flex';
footer.style.justifyContent = 'flex-end';
footer.style.paddingTop = '12px';
const btn = document.createElement('button');
btn.type = 'button';
btn.textContent = 'Schließen';
btn.setAttribute('data-bridge-viewcode-close', '1');
btn.setAttribute('data-bridge-modal-close', '1');
btn.style.padding = '6px 12px';
btn.style.border = '1px solid #cbd5f5';
btn.style.borderRadius = '4px';
btn.style.background = '#f8fafc';
btn.style.cursor = 'pointer';
btn.addEventListener('click', () => {
if (B.allowModalCloseOnce) B.allowModalCloseOnce();
if (ed.Modal && ed.Modal.close) ed.Modal.close();
});
footer.appendChild(btn);
contentEl.appendChild(footer);
} catch {}
}, 0);
});
}
}
        
        // 🛑 KRITISCHE KORREKTUR 1: Explizite Erstellung aller konfigurierten Kategorien
        ensureConfiguredCategories(ed);