update
This commit is contained in:
22
README.md
22
README.md
@@ -1,5 +1,11 @@
|
|||||||
# Mailadmin – Email Template System (R17 modular)
|
# 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
|
## Struktur
|
||||||
mailadmin/
|
mailadmin/
|
||||||
@@ -23,8 +29,12 @@ mailadmin/
|
|||||||
ui-editor.js # Editor-Dialog + Handshake
|
ui-editor.js # Editor-Dialog + Handshake
|
||||||
editor/
|
editor/
|
||||||
editor-core.php # LÄDT NUR LOKAL (kein CDN)
|
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
|
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)
|
## Vendor (lokal bereitstellen)
|
||||||
Lege die GrapesJS-Dateien lokal ab (kein CDN):
|
Lege die GrapesJS-Dateien lokal ab (kein CDN):
|
||||||
@@ -36,10 +46,16 @@ Lege die GrapesJS-Dateien lokal ab (kein CDN):
|
|||||||
Nutze `schema.sql` (inkl. *NULL‑fähiger* Fremdschlüssel & ON DELETE SET NULL).
|
Nutze `schema.sql` (inkl. *NULL‑fähiger* Fremdschlüssel & ON DELETE SET NULL).
|
||||||
Prefix standardmäßig: `emailtemplate_`.
|
Prefix standardmäßig: `emailtemplate_`.
|
||||||
|
|
||||||
|
## Editor-Flags (window.BridgeParts)
|
||||||
|
- ENABLE_EDITOR_EXTENSIONS
|
||||||
|
- ENABLE_EDITOR_BEHAVIOR
|
||||||
|
- ENABLE_PLACEHOLDERS
|
||||||
|
- ENABLE_TABLE_BUILDER
|
||||||
|
- ENABLE_RTE
|
||||||
|
|
||||||
## Schnellstart
|
## Schnellstart
|
||||||
1) `mailadmin/inc/config.php` anlegen (siehe `config.example.php`).
|
1) `mailadmin/inc/config.php` anlegen (siehe `config.example.php`).
|
||||||
2) `schema.sql` in deiner Templates-Datenbank ausführen.
|
2) `schema.sql` in deiner Templates-Datenbank ausführen.
|
||||||
3) Vendor-Dateien in `public/vendor/...` kopieren.
|
3) Vendor-Dateien in `public/vendor/...` kopieren.
|
||||||
4) `public/tools/config-doctor.php` & `public/api.php?action=health` prüfen.
|
4) `public/tools/config-doctor.php` & `public/api.php?action=health` prüfen.
|
||||||
5) `public/index.php` öffnen → „Neu …“, „Vorschau“, „Im E‑Mail‑Editor öffnen“ etc.
|
5) `public/index.php` öffnen → „Neu …“, „Vorschau“, „Im E‑Mail‑Editor öffnen“ etc.
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -32,13 +32,17 @@
|
|||||||
window.__bridgeModalGuardsInstalled = true;
|
window.__bridgeModalGuardsInstalled = true;
|
||||||
window.__bridgeModalAllowClose = false;
|
window.__bridgeModalAllowClose = false;
|
||||||
|
|
||||||
|
const allowCloseOnce = () => {
|
||||||
|
window.__bridgeModalAllowClose = true;
|
||||||
|
setTimeout(() => { window.__bridgeModalAllowClose = false; }, 0);
|
||||||
|
};
|
||||||
|
|
||||||
document.addEventListener('click', (evt) => {
|
document.addEventListener('click', (evt) => {
|
||||||
const closeHit = evt.target && evt.target.closest
|
const closeHit = evt.target && evt.target.closest
|
||||||
? evt.target.closest('.gjs-mdl-btn-close,[data-bridge-modal-close="1"]')
|
? evt.target.closest('.gjs-mdl-btn-close,[data-bridge-modal-close="1"]')
|
||||||
: null;
|
: null;
|
||||||
if (closeHit) {
|
if (closeHit) {
|
||||||
window.__bridgeModalAllowClose = true;
|
allowCloseOnce();
|
||||||
setTimeout(() => { window.__bridgeModalAllowClose = false; }, 0);
|
|
||||||
}
|
}
|
||||||
const container = evt.target && evt.target.closest
|
const container = evt.target && evt.target.closest
|
||||||
? evt.target.closest('.gjs-mdl-container')
|
? evt.target.closest('.gjs-mdl-container')
|
||||||
@@ -53,6 +57,15 @@
|
|||||||
}
|
}
|
||||||
}, true);
|
}, 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) => {
|
document.addEventListener('keydown', (evt) => {
|
||||||
if (evt.key !== 'Escape') return;
|
if (evt.key !== 'Escape') return;
|
||||||
const hasModal = document.querySelector('.gjs-mdl-container');
|
const hasModal = document.querySelector('.gjs-mdl-container');
|
||||||
@@ -603,9 +616,9 @@
|
|||||||
try {
|
try {
|
||||||
const modalEl = ed.Modal && ed.Modal.el;
|
const modalEl = ed.Modal && ed.Modal.el;
|
||||||
if (!modalEl) return;
|
if (!modalEl) return;
|
||||||
if (modalEl.querySelector('[data-bridge-viewcode-close]')) return;
|
|
||||||
const contentEl = modalEl.querySelector('.gjs-mdl-content');
|
const contentEl = modalEl.querySelector('.gjs-mdl-content');
|
||||||
if (!contentEl) return;
|
if (!contentEl) return;
|
||||||
|
if (modalEl.querySelector('[data-bridge-viewcode-close]')) return;
|
||||||
const footer = document.createElement('div');
|
const footer = document.createElement('div');
|
||||||
footer.style.display = 'flex';
|
footer.style.display = 'flex';
|
||||||
footer.style.justifyContent = 'flex-end';
|
footer.style.justifyContent = 'flex-end';
|
||||||
@@ -629,6 +642,45 @@
|
|||||||
} catch {}
|
} 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
|
// 🛑 KRITISCHE KORREKTUR 1: Explizite Erstellung aller konfigurierten Kategorien
|
||||||
ensureConfiguredCategories(ed);
|
ensureConfiguredCategories(ed);
|
||||||
|
|||||||
Reference in New Issue
Block a user