asdasd
This commit is contained in:
1
debug/.gitkeep
Normal file
1
debug/.gitkeep
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -60,6 +60,7 @@ if ($debugRedirect) {
|
|||||||
<script>
|
<script>
|
||||||
window.APP_BASE_URL = <?= json_encode($appBaseUrl, JSON_UNESCAPED_SLASHES) ?>;
|
window.APP_BASE_URL = <?= json_encode($appBaseUrl, JSON_UNESCAPED_SLASHES) ?>;
|
||||||
window.APP_API_BASE = <?= json_encode($appApiBase, JSON_UNESCAPED_SLASHES) ?>;
|
window.APP_API_BASE = <?= json_encode($appApiBase, JSON_UNESCAPED_SLASHES) ?>;
|
||||||
|
window.APP_ENV = <?= json_encode($layoutContext['app_env'] ?? '', JSON_UNESCAPED_SLASHES) ?>;
|
||||||
<?php if ($debugRedirect): ?>
|
<?php if ($debugRedirect): ?>
|
||||||
window.DISABLE_AUTH_REDIRECT = true;
|
window.DISABLE_AUTH_REDIRECT = true;
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|||||||
@@ -109,22 +109,19 @@
|
|||||||
content:`<a href="#" data-gjs-type="button" style="${css({display:'inline-block','background-color':'#0ea5e9',color:'#fff','text-decoration':'none',padding:'10px 18px','border-radius':'6px','font-family':'Arial,sans-serif','font-size':'14px'})}">Button</a>` });
|
content:`<a href="#" data-gjs-type="button" style="${css({display:'inline-block','background-color':'#0ea5e9',color:'#fff','text-decoration':'none',padding:'10px 18px','border-radius':'6px','font-family':'Arial,sans-serif','font-size':'14px'})}">Button</a>` });
|
||||||
|
|
||||||
// TABLE (Registriert als 'std-table')
|
// TABLE (Registriert als 'std-table')
|
||||||
addOrUpdate(bm, 'std-table', { label:'Tabelle (Basis)',
|
addOrUpdate(bm, 'std-table', { label:'Tabelle (2xN)',
|
||||||
content:`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="${css({'font-family':'Arial,sans-serif','border-collapse':'collapse','width':'100%'})}">
|
content:`<table data-gjs-type="bridge-table" data-bridge-rows="3" data-bridge-cols="2" role="presentation" width="100%" cellpadding="0" cellspacing="0" style="${css({'font-family':'Arial,sans-serif','border-collapse':'collapse','width':'100%'})}">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="${css({'text-align':'left','padding':'8px','border':'1px solid #e2e8f0','background-color':'#f8fafc','font-size':'13px'})}">Spalte A</th>
|
<th style="${css({'text-align':'left','padding':'8px','border':'1px solid #e2e8f0','background-color':'#f8fafc','font-size':'13px'})}">Spalte A</th>
|
||||||
<th style="${css({'text-align':'left','padding':'8px','border':'1px solid #e2e8f0','background-color':'#f8fafc','font-size':'13px'})}">Spalte B</th>
|
<th style="${css({'text-align':'left','padding':'8px','border':'1px solid #e2e8f0','background-color':'#f8fafc','font-size':'13px'})}">Spalte B</th>
|
||||||
<th style="${css({'text-align':'left','padding':'8px','border':'1px solid #e2e8f0','background-color':'#f8fafc','font-size':'13px'})}">Spalte C</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="${css({'padding':'8px','border':'1px solid #e2e8f0','font-size':'13px'})}">Zeile 1</td>
|
<td style="${css({'padding':'8px','border':'1px solid #e2e8f0','font-size':'13px'})}">Zeile 1</td>
|
||||||
<td style="${css({'padding':'8px','border':'1px solid #e2e8f0','font-size':'13px'})}">...</td>
|
<td style="${css({'padding':'8px','border':'1px solid #e2e8f0','font-size':'13px'})}">...</td>
|
||||||
<td style="${css({'padding':'8px','border':'1px solid #e2e8f0','font-size':'13px'})}">...</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="${css({'padding':'8px','border':'1px solid #e2e8f0','font-size':'13px'})}">Zeile 2</td>
|
<td style="${css({'padding':'8px','border':'1px solid #e2e8f0','font-size':'13px'})}">Zeile 2</td>
|
||||||
<td style="${css({'padding':'8px','border':'1px solid #e2e8f0','font-size':'13px'})}">...</td>
|
<td style="${css({'padding':'8px','border':'1px solid #e2e8f0','font-size':'13px'})}">...</td>
|
||||||
<td style="${css({'padding':'8px','border':'1px solid #e2e8f0','font-size':'13px'})}">...</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</table>` });
|
</table>` });
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const state = {
|
|||||||
|
|
||||||
const pageType = document.body?.dataset?.page || 'account';
|
const pageType = document.body?.dataset?.page || 'account';
|
||||||
const DEBUG_EMAIL = 'madmin@papa-kind-treff.info';
|
const DEBUG_EMAIL = 'madmin@papa-kind-treff.info';
|
||||||
|
const DEBUG_ENV = (window.APP_ENV || '').toLowerCase();
|
||||||
const MAX_CONSOLE_LINES = 200;
|
const MAX_CONSOLE_LINES = 200;
|
||||||
|
|
||||||
let avatarBtn;
|
let avatarBtn;
|
||||||
@@ -30,9 +31,12 @@ let debugButton;
|
|||||||
let debugDialog;
|
let debugDialog;
|
||||||
let debugPhpLoaded = false;
|
let debugPhpLoaded = false;
|
||||||
let debugPhpLoading = false;
|
let debugPhpLoading = false;
|
||||||
|
let debugLogsLoaded = false;
|
||||||
let debugActiveTab = 'php';
|
let debugActiveTab = 'php';
|
||||||
let phpInfoContainer;
|
let phpInfoContainer;
|
||||||
let consoleContainer;
|
let consoleContainer;
|
||||||
|
let logsListContainer;
|
||||||
|
let logDetailContainer;
|
||||||
let debugStylesInjected = false;
|
let debugStylesInjected = false;
|
||||||
let consolePatched = false;
|
let consolePatched = false;
|
||||||
const consoleBuffer = [];
|
const consoleBuffer = [];
|
||||||
@@ -695,7 +699,8 @@ function escapeHtml(str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function refreshDebugAccess() {
|
function refreshDebugAccess() {
|
||||||
const allowed = (window.__currentUser?.email || '').toLowerCase() === DEBUG_EMAIL;
|
const isStaging = DEBUG_ENV === 'staging';
|
||||||
|
const allowed = isStaging && (window.__currentUser?.email || '').toLowerCase() === DEBUG_EMAIL;
|
||||||
if (!allowed) {
|
if (!allowed) {
|
||||||
debugButton?.remove();
|
debugButton?.remove();
|
||||||
debugButton = null;
|
debugButton = null;
|
||||||
@@ -738,6 +743,11 @@ function ensureDebugStyles() {
|
|||||||
.debug-console-entry.log{color:#15803d}
|
.debug-console-entry.log{color:#15803d}
|
||||||
.debug-console-entry.warn{color:#b45309}
|
.debug-console-entry.warn{color:#b45309}
|
||||||
.debug-console-entry.error{color:#b91c1c}
|
.debug-console-entry.error{color:#b91c1c}
|
||||||
|
.debug-logs-grid{display:grid;grid-template-columns:220px 1fr;gap:12px;height:100%}
|
||||||
|
.debug-logs-list{background:#fff;border:1px solid #e2e8f0;border-radius:.75rem;padding:.5rem;overflow:auto}
|
||||||
|
.debug-logs-list button{width:100%;text-align:left;border:none;background:transparent;padding:.35rem .5rem;border-radius:.5rem;cursor:pointer}
|
||||||
|
.debug-logs-list button.active{background:#e0f2fe;color:#0c4a6e}
|
||||||
|
.debug-logs-detail{background:#fff;border:1px solid #e2e8f0;border-radius:.75rem;padding:.75rem;overflow:auto;font-family:monospace;font-size:.85rem;white-space:pre-wrap}
|
||||||
`;
|
`;
|
||||||
document.head.appendChild(style);
|
document.head.appendChild(style);
|
||||||
debugStylesInjected = true;
|
debugStylesInjected = true;
|
||||||
@@ -756,6 +766,7 @@ function ensureDebugDialog() {
|
|||||||
<div class="debug-tabs">
|
<div class="debug-tabs">
|
||||||
<button type="button" data-debug-tab="php" class="active">PHP Debug</button>
|
<button type="button" data-debug-tab="php" class="active">PHP Debug</button>
|
||||||
<button type="button" data-debug-tab="console">Console</button>
|
<button type="button" data-debug-tab="console">Console</button>
|
||||||
|
<button type="button" data-debug-tab="logs">Logs</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="debug-panel" data-debug-panel="php">
|
<div class="debug-panel" data-debug-panel="php">
|
||||||
<div id="debugPhpContent" class="text-sm text-slate-700">Lade Daten…</div>
|
<div id="debugPhpContent" class="text-sm text-slate-700">Lade Daten…</div>
|
||||||
@@ -763,10 +774,18 @@ function ensureDebugDialog() {
|
|||||||
<div class="debug-panel hidden" data-debug-panel="console">
|
<div class="debug-panel hidden" data-debug-panel="console">
|
||||||
<pre id="debugConsoleContent"></pre>
|
<pre id="debugConsoleContent"></pre>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="debug-panel hidden" data-debug-panel="logs">
|
||||||
|
<div class="debug-logs-grid">
|
||||||
|
<div class="debug-logs-list" id="debugLogsList">Keine Logs geladen.</div>
|
||||||
|
<div class="debug-logs-detail" id="debugLogDetail">Bitte Log auswaehlen.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
document.body.appendChild(debugDialog);
|
document.body.appendChild(debugDialog);
|
||||||
phpInfoContainer = debugDialog.querySelector('#debugPhpContent');
|
phpInfoContainer = debugDialog.querySelector('#debugPhpContent');
|
||||||
consoleContainer = debugDialog.querySelector('#debugConsoleContent');
|
consoleContainer = debugDialog.querySelector('#debugConsoleContent');
|
||||||
|
logsListContainer = debugDialog.querySelector('#debugLogsList');
|
||||||
|
logDetailContainer = debugDialog.querySelector('#debugLogDetail');
|
||||||
debugDialog.querySelector('[data-debug-close]')?.addEventListener('click', () => closeDebugDialog());
|
debugDialog.querySelector('[data-debug-close]')?.addEventListener('click', () => closeDebugDialog());
|
||||||
debugDialog.addEventListener('close', () => setDebugTab('php'));
|
debugDialog.addEventListener('close', () => setDebugTab('php'));
|
||||||
debugDialog.querySelectorAll('[data-debug-tab]').forEach(btn => {
|
debugDialog.querySelectorAll('[data-debug-tab]').forEach(btn => {
|
||||||
@@ -801,8 +820,10 @@ function setDebugTab(tab) {
|
|||||||
});
|
});
|
||||||
if (debugActiveTab === 'php') {
|
if (debugActiveTab === 'php') {
|
||||||
loadPhpInfo();
|
loadPhpInfo();
|
||||||
} else {
|
} else if (debugActiveTab === 'console') {
|
||||||
renderConsolePanel();
|
renderConsolePanel();
|
||||||
|
} else if (debugActiveTab === 'logs') {
|
||||||
|
loadDebugLogs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -842,6 +863,49 @@ function renderConsolePanel() {
|
|||||||
consoleContainer.innerHTML = lines.join('');
|
consoleContainer.innerHTML = lines.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadDebugLogs() {
|
||||||
|
if (debugLogsLoaded || !logsListContainer || !logDetailContainer) return;
|
||||||
|
logsListContainer.textContent = 'Lade Logs…';
|
||||||
|
logDetailContainer.textContent = 'Bitte Log auswaehlen.';
|
||||||
|
try {
|
||||||
|
const res = await apiAction('debug.logs.list', { method: 'GET' });
|
||||||
|
if (!res?.ok) throw new Error(res?.error || 'Logs konnten nicht geladen werden');
|
||||||
|
const items = Array.isArray(res.items) ? res.items : [];
|
||||||
|
if (!items.length) {
|
||||||
|
logsListContainer.textContent = 'Keine Logs vorhanden.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logsListContainer.innerHTML = '';
|
||||||
|
items.forEach((item, idx) => {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.textContent = item.name || item.file || `Log ${idx + 1}`;
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
logsListContainer.querySelectorAll('button').forEach(b => b.classList.remove('active'));
|
||||||
|
btn.classList.add('active');
|
||||||
|
loadDebugLogFile(item.name || item.file || '');
|
||||||
|
});
|
||||||
|
logsListContainer.appendChild(btn);
|
||||||
|
if (idx === 0) btn.click();
|
||||||
|
});
|
||||||
|
debugLogsLoaded = true;
|
||||||
|
} catch (err) {
|
||||||
|
logsListContainer.textContent = err.message || 'Logs konnten nicht geladen werden';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadDebugLogFile(name) {
|
||||||
|
if (!name || !logDetailContainer) return;
|
||||||
|
logDetailContainer.textContent = 'Lade Log…';
|
||||||
|
try {
|
||||||
|
const res = await apiAction('debug.logs.read', { method: 'GET', data: { name } });
|
||||||
|
if (!res?.ok) throw new Error(res?.error || 'Log konnte nicht geladen werden');
|
||||||
|
logDetailContainer.textContent = res.content || '(leer)';
|
||||||
|
} catch (err) {
|
||||||
|
logDetailContainer.textContent = err.message || 'Log konnte nicht geladen werden';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function ensureConsoleCapture() {
|
function ensureConsoleCapture() {
|
||||||
if (consolePatched) return;
|
if (consolePatched) return;
|
||||||
['log', 'warn', 'error'].forEach(type => {
|
['log', 'warn', 'error'].forEach(type => {
|
||||||
|
|||||||
@@ -301,18 +301,21 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ensureTextToolbarButton = (editor, component) => {
|
const ensureTextToolbarButton = (editor, component) => {
|
||||||
if (!component || !component.is || !component.is('text')) return;
|
if (!component || !component.is) return;
|
||||||
|
const isTextLike = component.is('text') || component.is('button') || component.is('link');
|
||||||
|
if (!isTextLike) return;
|
||||||
const toolbar = (component.get && component.get('toolbar')) || [];
|
const toolbar = (component.get && component.get('toolbar')) || [];
|
||||||
if (toolbar.some((item) => item && item.command === 'bridge-open-richtext')) return;
|
if (toolbar.some((item) => item && item.command === 'bridge-open-richtext')) return;
|
||||||
toolbar.push({
|
toolbar.push({
|
||||||
attributes: { class: 'fa fa-pencil', title: 'Richtext bearbeiten' },
|
label: '<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="M4 20h4l10-10-4-4L4 16v4zm14.7-11.3c.4-.4.4-1 0-1.4l-2-2c-.4-.4-1-.4-1.4 0l-1.3 1.3 4 4 1.7-1.9z" fill="currentColor"/></svg>',
|
||||||
|
attributes: { title: 'Richtext bearbeiten' },
|
||||||
command: 'bridge-open-richtext',
|
command: 'bridge-open-richtext',
|
||||||
});
|
});
|
||||||
component.set && component.set('toolbar', toolbar);
|
component.set && component.set('toolbar', toolbar);
|
||||||
};
|
};
|
||||||
|
|
||||||
const openRichTextModal = (editor, component) => {
|
const openRichTextModal = (editor, component) => {
|
||||||
if (!component || !component.is || !component.is('text')) {
|
if (!component || !component.is || !(component.is('text') || component.is('button') || component.is('link'))) {
|
||||||
log('RTE', 'Bitte zuerst ein Text-Element auswaehlen.', '#888');
|
log('RTE', 'Bitte zuerst ein Text-Element auswaehlen.', '#888');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -480,7 +483,10 @@
|
|||||||
cancelBtn.style.borderRadius = '4px';
|
cancelBtn.style.borderRadius = '4px';
|
||||||
cancelBtn.style.background = '#f8fafc';
|
cancelBtn.style.background = '#f8fafc';
|
||||||
cancelBtn.style.cursor = 'pointer';
|
cancelBtn.style.cursor = 'pointer';
|
||||||
cancelBtn.addEventListener('click', () => modal.close());
|
cancelBtn.addEventListener('click', () => {
|
||||||
|
editor.__bridgeRteModalOpen = false;
|
||||||
|
modal.close();
|
||||||
|
});
|
||||||
|
|
||||||
const saveBtn = doc.createElement('button');
|
const saveBtn = doc.createElement('button');
|
||||||
saveBtn.type = 'button';
|
saveBtn.type = 'button';
|
||||||
@@ -509,6 +515,7 @@
|
|||||||
editor.trigger('component:update', component);
|
editor.trigger('component:update', component);
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
|
editor.__bridgeRteModalOpen = false;
|
||||||
modal.close();
|
modal.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -630,10 +637,11 @@
|
|||||||
'bridge-placeholder',
|
'bridge-placeholder',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const isTextLike = (model) => !!(model && model.is && (model.is('text') || model.is('button') || model.is('link')));
|
||||||
let lastTextSelection = { id: null, ts: 0 };
|
let lastTextSelection = { id: null, ts: 0 };
|
||||||
editor.on('component:selected', (model) => {
|
editor.on('component:selected', (model) => {
|
||||||
ensureTextToolbarButton(editor, model);
|
ensureTextToolbarButton(editor, model);
|
||||||
if (!model || !model.is || !model.is('text')) return;
|
if (!isTextLike(model)) return;
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (lastTextSelection.id === model.cid && (now - lastTextSelection.ts) < 450) {
|
if (lastTextSelection.id === model.cid && (now - lastTextSelection.ts) < 450) {
|
||||||
openRichTextModal(editor, model);
|
openRichTextModal(editor, model);
|
||||||
@@ -642,7 +650,7 @@
|
|||||||
});
|
});
|
||||||
editor.on('component:add', (model) => ensureTextToolbarButton(editor, model));
|
editor.on('component:add', (model) => ensureTextToolbarButton(editor, model));
|
||||||
editor.on('component:dblclick', (model) => {
|
editor.on('component:dblclick', (model) => {
|
||||||
if (model && model.is && model.is('text')) {
|
if (isTextLike(model)) {
|
||||||
openRichTextModal(editor, model);
|
openRichTextModal(editor, model);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -652,13 +660,193 @@
|
|||||||
body.__bridgeRteDblclickBound = true;
|
body.__bridgeRteDblclickBound = true;
|
||||||
body.addEventListener('dblclick', () => {
|
body.addEventListener('dblclick', () => {
|
||||||
const selected = editor.getSelected && editor.getSelected();
|
const selected = editor.getSelected && editor.getSelected();
|
||||||
if (selected && selected.is && selected.is('text')) {
|
if (isTextLike(selected)) {
|
||||||
openRichTextModal(editor, selected);
|
openRichTextModal(editor, selected);
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setupTableBuilder = (editor) => {
|
||||||
|
const domc = editor.DomComponents;
|
||||||
|
if (!domc) return;
|
||||||
|
const icon = (path) => `<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="${path}" fill="currentColor"/></svg>`;
|
||||||
|
const tableIcon = icon('M3 4h18v16H3V4zm2 2v3h6V6H5zm8 0v3h6V6h-6zM5 11v3h6v-3H5zm8 0v3h6v-3h-6zM5 16v2h6v-2H5zm8 0v2h6v-2h-6z');
|
||||||
|
|
||||||
|
const collectTableCells = (component) => {
|
||||||
|
const el = component?.view?.el;
|
||||||
|
if (!el) return [];
|
||||||
|
return Array.from(el.querySelectorAll('tr')).map(row =>
|
||||||
|
Array.from(row.children || []).map(cell => cell.innerHTML || '')
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildTableHtml = (rows, cols, existing) => {
|
||||||
|
const safeRows = Math.max(1, Math.min(20, Number(rows) || 1));
|
||||||
|
const safeCols = Math.max(2, Math.min(2, Number(cols) || 2));
|
||||||
|
const cellStyle = "padding:8px;border:1px solid #e2e8f0;font-size:13px";
|
||||||
|
const headStyle = "text-align:left;padding:8px;border:1px solid #e2e8f0;background-color:#f8fafc;font-size:13px";
|
||||||
|
let html = '';
|
||||||
|
for (let r = 0; r < safeRows; r++) {
|
||||||
|
html += '<tr>';
|
||||||
|
for (let c = 0; c < safeCols; c++) {
|
||||||
|
const existingVal = existing?.[r]?.[c] || '';
|
||||||
|
const label = existingVal || (r === 0 ? `Spalte ${String.fromCharCode(65 + c)}` : `Zeile ${r} / ${c + 1}`);
|
||||||
|
if (r === 0) {
|
||||||
|
html += `<th style="${headStyle}">${label}</th>`;
|
||||||
|
} else {
|
||||||
|
html += `<td style="${cellStyle}">${label}</td>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
html += '</tr>';
|
||||||
|
}
|
||||||
|
return html;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openTableModal = (component) => {
|
||||||
|
if (!component) return;
|
||||||
|
const modal = editor.Modal;
|
||||||
|
if (!modal) return;
|
||||||
|
const attrs = component.getAttributes ? component.getAttributes() : {};
|
||||||
|
const rows = Number(attrs['data-bridge-rows'] || 3) || 3;
|
||||||
|
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.style.display = 'flex';
|
||||||
|
container.style.flexDirection = 'column';
|
||||||
|
container.style.gap = '12px';
|
||||||
|
container.style.minWidth = '280px';
|
||||||
|
|
||||||
|
const label = document.createElement('label');
|
||||||
|
label.textContent = 'Anzahl Zeilen';
|
||||||
|
label.style.fontSize = '13px';
|
||||||
|
label.style.fontWeight = '600';
|
||||||
|
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'number';
|
||||||
|
input.min = '1';
|
||||||
|
input.max = '20';
|
||||||
|
input.value = String(rows);
|
||||||
|
input.style.width = '100%';
|
||||||
|
input.style.padding = '6px 8px';
|
||||||
|
input.style.border = '1px solid #cbd5f5';
|
||||||
|
input.style.borderRadius = '4px';
|
||||||
|
|
||||||
|
label.appendChild(input);
|
||||||
|
container.appendChild(label);
|
||||||
|
|
||||||
|
const actions = document.createElement('div');
|
||||||
|
actions.style.display = 'flex';
|
||||||
|
actions.style.justifyContent = 'flex-end';
|
||||||
|
actions.style.gap = '8px';
|
||||||
|
|
||||||
|
const cancelBtn = document.createElement('button');
|
||||||
|
cancelBtn.type = 'button';
|
||||||
|
cancelBtn.textContent = 'Abbrechen';
|
||||||
|
cancelBtn.className = 'btn';
|
||||||
|
cancelBtn.addEventListener('click', () => modal.close());
|
||||||
|
|
||||||
|
const saveBtn = document.createElement('button');
|
||||||
|
saveBtn.type = 'button';
|
||||||
|
saveBtn.textContent = 'Uebernehmen';
|
||||||
|
saveBtn.className = 'btn';
|
||||||
|
saveBtn.addEventListener('click', () => {
|
||||||
|
const nextRows = Math.max(1, Math.min(20, Number(input.value) || 1));
|
||||||
|
const existing = collectTableCells(component);
|
||||||
|
const html = buildTableHtml(nextRows, 2, existing);
|
||||||
|
component.addAttributes && component.addAttributes({
|
||||||
|
'data-bridge-rows': String(nextRows),
|
||||||
|
'data-bridge-cols': '2',
|
||||||
|
});
|
||||||
|
if (component.components) {
|
||||||
|
component.components(html);
|
||||||
|
}
|
||||||
|
if (component.view && component.view.render) {
|
||||||
|
component.view.render();
|
||||||
|
}
|
||||||
|
modal.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
actions.appendChild(cancelBtn);
|
||||||
|
actions.appendChild(saveBtn);
|
||||||
|
container.appendChild(actions);
|
||||||
|
|
||||||
|
modal.setTitle('Tabelle konfigurieren');
|
||||||
|
modal.setContent(container);
|
||||||
|
const mdl = modal.getModel && modal.getModel();
|
||||||
|
if (mdl && typeof mdl.set === 'function') {
|
||||||
|
mdl.set('closeOnEsc', false);
|
||||||
|
mdl.set('closeOnClick', false);
|
||||||
|
}
|
||||||
|
modal.open();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!editor.Commands.get('bridge-table:edit')) {
|
||||||
|
editor.Commands.add('bridge-table:edit', {
|
||||||
|
run(ed, sender, opts = {}) {
|
||||||
|
if (sender && sender.set) sender.set('active', 0);
|
||||||
|
const component = opts.component || ed.getSelected();
|
||||||
|
if (component && component.is && component.is('bridge-table')) {
|
||||||
|
openTableModal(component);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!domc.getType('bridge-table')) {
|
||||||
|
const baseType = domc.getType('table') || domc.getType('default');
|
||||||
|
const BaseModel = baseType.model;
|
||||||
|
const BaseView = baseType.view;
|
||||||
|
domc.addType('bridge-table', {
|
||||||
|
model: BaseModel.extend({
|
||||||
|
defaults: Object.assign({}, (typeof BaseModel.prototype.defaults === 'function' ? BaseModel.prototype.defaults() : (BaseModel.prototype.defaults || {})), {
|
||||||
|
tagName: 'table',
|
||||||
|
attributes: {
|
||||||
|
'data-bridge-table': '1',
|
||||||
|
'data-bridge-rows': '3',
|
||||||
|
'data-bridge-cols': '2',
|
||||||
|
role: 'presentation',
|
||||||
|
},
|
||||||
|
toolbar: [
|
||||||
|
{
|
||||||
|
label: tableIcon,
|
||||||
|
command: 'bridge-table:edit',
|
||||||
|
attributes: { title: 'Tabelle bearbeiten' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
view: BaseView,
|
||||||
|
isComponent: el => {
|
||||||
|
if (el.tagName === 'TABLE' && el.getAttribute('data-bridge-table')) {
|
||||||
|
return { type: 'bridge-table' };
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.on('component:selected', (model) => {
|
||||||
|
if (model && model.is && model.is('bridge-table')) {
|
||||||
|
const toolbar = model.get('toolbar') || [];
|
||||||
|
const exists = toolbar.some(btn => btn && btn.command === 'bridge-table:edit');
|
||||||
|
if (!exists) {
|
||||||
|
toolbar.push({
|
||||||
|
label: tableIcon,
|
||||||
|
command: 'bridge-table:edit',
|
||||||
|
attributes: { title: 'Tabelle bearbeiten' },
|
||||||
|
});
|
||||||
|
model.set('toolbar', toolbar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.on('component:dblclick', (model) => {
|
||||||
|
if (model && model.is && model.is('bridge-table')) {
|
||||||
|
openTableModal(model);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const loadDynamicFonts = async () => {
|
const loadDynamicFonts = async () => {
|
||||||
try {
|
try {
|
||||||
const base = B.API_KERNEL_URL || '/api.php';
|
const base = B.API_KERNEL_URL || '/api.php';
|
||||||
@@ -746,6 +934,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupRichTextEditor(ed);
|
setupRichTextEditor(ed);
|
||||||
|
setupTableBuilder(ed);
|
||||||
loadDynamicFonts();
|
loadDynamicFonts();
|
||||||
|
|
||||||
// Entfernt: jegliche Blur/RTE-Handler, die Inhalte verändern.
|
// Entfernt: jegliche Blur/RTE-Handler, die Inhalte verändern.
|
||||||
|
|||||||
@@ -788,6 +788,16 @@ class ApiKernel
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->dispatchTestMail($recipient, $subject, $html, $sender)) {
|
if (!$this->dispatchTestMail($recipient, $subject, $html, $sender)) {
|
||||||
|
$this->writeDebugLog('templates_test_send', [
|
||||||
|
'time' => date(DATE_ATOM),
|
||||||
|
'template_id' => $templateId,
|
||||||
|
'to' => $recipient,
|
||||||
|
'subject' => $subject,
|
||||||
|
'sender_id' => $senderId > 0 ? $senderId : null,
|
||||||
|
'from_email' => $sender['from_email'] ?? ($this->conf['smtp']['from_email'] ?? null),
|
||||||
|
'from_name' => $sender['from_name'] ?? ($this->conf['smtp']['from_name'] ?? null),
|
||||||
|
'html_length' => strlen($html),
|
||||||
|
]);
|
||||||
$this->fail('Send failed', null, 500);
|
$this->fail('Send failed', null, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1084,6 +1094,12 @@ class ApiKernel
|
|||||||
case 'account.fonts.list':
|
case 'account.fonts.list':
|
||||||
$this->handleAccountFontsList();
|
$this->handleAccountFontsList();
|
||||||
break;
|
break;
|
||||||
|
case 'debug.logs.list':
|
||||||
|
$this->handleDebugLogsList();
|
||||||
|
break;
|
||||||
|
case 'debug.logs.read':
|
||||||
|
$this->handleDebugLogsRead();
|
||||||
|
break;
|
||||||
case 'placeholders.status':
|
case 'placeholders.status':
|
||||||
$this->handlePlaceholderStatus();
|
$this->handlePlaceholderStatus();
|
||||||
break;
|
break;
|
||||||
@@ -2141,12 +2157,54 @@ class ApiKernel
|
|||||||
{
|
{
|
||||||
$user = $this->requireAuth();
|
$user = $this->requireAuth();
|
||||||
$this->ensureDebugUser($user);
|
$this->ensureDebugUser($user);
|
||||||
|
$this->ensureDebugEnv();
|
||||||
ob_start();
|
ob_start();
|
||||||
phpinfo(INFO_GENERAL | INFO_CONFIGURATION | INFO_MODULES | INFO_ENVIRONMENT);
|
phpinfo(INFO_GENERAL | INFO_CONFIGURATION | INFO_MODULES | INFO_ENVIRONMENT);
|
||||||
$html = ob_get_clean() ?: '';
|
$html = ob_get_clean() ?: '';
|
||||||
$this->respond(['ok' => true, 'html' => $html]);
|
$this->respond(['ok' => true, 'html' => $html]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function handleDebugLogsList(): void
|
||||||
|
{
|
||||||
|
$user = $this->requireAuth();
|
||||||
|
$this->ensureDebugUser($user);
|
||||||
|
$this->ensureDebugEnv();
|
||||||
|
$dir = $this->debugDir();
|
||||||
|
if (!is_dir($dir)) {
|
||||||
|
$this->respond(['ok' => true, 'items' => []]);
|
||||||
|
}
|
||||||
|
$items = [];
|
||||||
|
foreach (glob($dir . '/*.log') ?: [] as $file) {
|
||||||
|
$items[] = [
|
||||||
|
'name' => basename($file),
|
||||||
|
'size' => filesize($file) ?: 0,
|
||||||
|
'updated_at' => date(DATE_ATOM, filemtime($file) ?: time()),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$this->respond(['ok' => true, 'items' => $items]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function handleDebugLogsRead(): void
|
||||||
|
{
|
||||||
|
$user = $this->requireAuth();
|
||||||
|
$this->ensureDebugUser($user);
|
||||||
|
$this->ensureDebugEnv();
|
||||||
|
$name = trim((string)($this->in['name'] ?? ''));
|
||||||
|
if ($name === '') {
|
||||||
|
$this->fail('Log name required', null, 422);
|
||||||
|
}
|
||||||
|
$name = preg_replace('/[^a-zA-Z0-9_\.\-]/', '', $name) ?: '';
|
||||||
|
if ($name === '' || strpos($name, '..') !== false) {
|
||||||
|
$this->fail('Invalid log name', null, 422);
|
||||||
|
}
|
||||||
|
$file = $this->debugDir() . '/' . $name;
|
||||||
|
if (!is_file($file)) {
|
||||||
|
$this->fail('Log not found', null, 404);
|
||||||
|
}
|
||||||
|
$content = (string)file_get_contents($file);
|
||||||
|
$this->respond(['ok' => true, 'content' => $content]);
|
||||||
|
}
|
||||||
|
|
||||||
private function resolveBridgeConfig(?int $customerId): array
|
private function resolveBridgeConfig(?int $customerId): array
|
||||||
{
|
{
|
||||||
$fileConf = $this->conf['placeholders']['bridge'] ?? [];
|
$fileConf = $this->conf['placeholders']['bridge'] ?? [];
|
||||||
@@ -2832,6 +2890,34 @@ SQL;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function ensureDebugEnv(): void
|
||||||
|
{
|
||||||
|
$env = strtolower((string)($this->conf['env'] ?? ''));
|
||||||
|
if ($env !== 'staging') {
|
||||||
|
$this->fail('Debug nur in Staging erlaubt', null, 403);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function debugDir(): string
|
||||||
|
{
|
||||||
|
return dirname(__DIR__) . '/debug';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function writeDebugLog(string $name, array $payload): void
|
||||||
|
{
|
||||||
|
if (strtolower((string)($this->conf['env'] ?? '')) !== 'staging') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$dir = $this->debugDir();
|
||||||
|
if (!is_dir($dir)) {
|
||||||
|
@mkdir($dir, 0775, true);
|
||||||
|
}
|
||||||
|
$safeName = preg_replace('/[^a-zA-Z0-9_\.\-]/', '_', $name) ?: 'debug';
|
||||||
|
$file = rtrim($dir, '/') . '/' . $safeName . '.log';
|
||||||
|
$data = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
||||||
|
@file_put_contents($file, $data ?: '');
|
||||||
|
}
|
||||||
|
|
||||||
private function defaultApiBase(): string
|
private function defaultApiBase(): string
|
||||||
{
|
{
|
||||||
$base = $this->conf['base_url'] ?? '';
|
$base = $this->conf['base_url'] ?? '';
|
||||||
|
|||||||
Reference in New Issue
Block a user