Keine Browser-Dialoge mehr — eigene dolModal-Helper [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s

Eddys Browser blockiert alert/confirm/prompt, deshalb sind die Dialoge
unsichtbar. Komplett umgestellt:

- js/editor.js: dolAlert/dolConfirm/dolPrompt als Promise-basierte Modals
  mit passendem Dark-Theme-Styling. Globaler click-Handler für
  data-dolconfirm an Links/Buttons.
- Alle alert()/confirm()/prompt() in editor.js ersetzt (Template speichern,
  Finalize, Upload, Löschen, Signatur-Verify usw.).
- bericht_card.php + admin/setup.php: onclick="return confirm(...)" durch
  data-dolconfirm="..." ersetzt.
- btn-finalize: butActionConfirm → butAction (Dolibarr erzeugt sonst
  automatisch einen leeren jQuery-UI-Confirm-Dialog
  #confirm-dialog-box-btn-finalize, unser eigener Handler macht das jetzt).
- css/bericht.css: Styling für .bericht-dolmodal*.
This commit is contained in:
Eduard Wisch 2026-04-09 15:46:25 +02:00
parent d40587845f
commit bf368122aa
4 changed files with 155 additions and 18 deletions

View file

@ -146,7 +146,7 @@ if (empty($templates)) {
print '<td>'.dol_print_size(filesize($path)).'</td>'; print '<td>'.dol_print_size(filesize($path)).'</td>';
print '<td class="right">'; print '<td class="right">';
print '<a href="?action=delete_template&name='.urlencode($tpl).'&token='.newToken().'" ' print '<a href="?action=delete_template&name='.urlencode($tpl).'&token='.newToken().'" '
.'onclick="return confirm(\'Vorlage löschen?\')" class="button-small">'.$langs->trans("Delete").'</a>'; .'data-dolconfirm="Vorlage löschen?" class="button-small">'.$langs->trans("Delete").'</a>';
print '</td>'; print '</td>';
print '</tr>'; print '</tr>';
} }

View file

@ -243,7 +243,7 @@ if (!$bericht) {
} }
if ($mode !== 'from_order' && $user->hasRight('bericht', 'delete')) { if ($mode !== 'from_order' && $user->hasRight('bericht', 'delete')) {
print '<a href="'.$_SERVER['PHP_SELF'].'?action=delete&berichtid='.$b->id.'&token='.newToken().'" ' print '<a href="'.$_SERVER['PHP_SELF'].'?action=delete&berichtid='.$b->id.'&token='.newToken().'" '
.'onclick="return confirm(\''.dol_escape_js($langs->trans("BerichtConfirmDelete")).'\')" ' .'data-dolconfirm="'.dol_escape_htmltag($langs->trans("BerichtConfirmDelete")).'" '
.'class="button-small button-delete">'.$langs->trans("Delete").'</a>'; .'class="button-small button-delete">'.$langs->trans("Delete").'</a>';
} }
print '</td>'; print '</td>';
@ -548,12 +548,12 @@ if (!$bericht) {
print '<button type="button" id="btn-preview" class="butAction" title="PDF-Vorschau ansehen ohne zu finalisieren">👁️ Vorschau</button>'; print '<button type="button" id="btn-preview" class="butAction" title="PDF-Vorschau ansehen ohne zu finalisieren">👁️ Vorschau</button>';
print '<button type="button" id="btn-save-as-template" class="butAction" title="Aktuellen Bericht als wiederverwendbare Vorlage speichern">📋 Als Vorlage</button>'; print '<button type="button" id="btn-save-as-template" class="butAction" title="Aktuellen Bericht als wiederverwendbare Vorlage speichern">📋 Als Vorlage</button>';
print '<a href="'.$_SERVER['PHP_SELF'].'?action=new_version&berichtid='.$bericht->id.'&token='.newToken().'" ' print '<a href="'.$_SERVER['PHP_SELF'].'?action=new_version&berichtid='.$bericht->id.'&token='.newToken().'" '
.'onclick="return confirm(\'Neue Version (v'.((int)$bericht->version + 1).') erstellen? Der aktuelle Bericht bleibt unverändert.\')" ' .'data-dolconfirm="Neue Version (v'.((int)$bericht->version + 1).') erstellen? Der aktuelle Bericht bleibt unverändert." '
.'class="butAction" title="Neue Version des Berichts anlegen, aktueller bleibt erhalten">🔀 Neue Version</a>'; .'class="butAction" title="Neue Version des Berichts anlegen, aktueller bleibt erhalten">🔀 Neue Version</a>';
print '<button type="button" id="btn-finalize" class="butActionConfirm" title="PDF erzeugen und unter Verknüpfte Dokumente ablegen">📑 '.$langs->trans("BerichtFinalize").'</button>'; print '<button type="button" id="btn-finalize" class="butAction" title="PDF erzeugen und unter Verknüpfte Dokumente ablegen">📑 '.$langs->trans("BerichtFinalize").'</button>';
if ($user->hasRight('bericht', 'delete')) { if ($user->hasRight('bericht', 'delete')) {
print '<a href="'.$_SERVER['PHP_SELF'].'?action=delete&berichtid='.$bericht->id.'&token='.newToken().'" ' print '<a href="'.$_SERVER['PHP_SELF'].'?action=delete&berichtid='.$bericht->id.'&token='.newToken().'" '
.'onclick="return confirm(\''.dol_escape_js($langs->trans("BerichtConfirmDelete")).'\')" ' .'data-dolconfirm="'.dol_escape_htmltag($langs->trans("BerichtConfirmDelete")).'" '
.'class="butActionDelete">🗑️ '.$langs->trans("Delete").'</a>'; .'class="butActionDelete">🗑️ '.$langs->trans("Delete").'</a>';
} }
print '</div>'; print '</div>';

View file

@ -494,3 +494,48 @@
.qr-url-display { word-break: break-all; } .qr-url-display { word-break: break-all; }
.qr-url-display a { color: var(--colortextlink, #7aa2f7); } .qr-url-display a { color: var(--colortextlink, #7aa2f7); }
.qr-status { padding: 8px; background: var(--colorbackbody, #1a1a1f); border-radius: 4px; margin-top: 12px; } .qr-status { padding: 8px; background: var(--colorbackbody, #1a1a1f); border-radius: 4px; margin-top: 12px; }
/* ---------- dolModal (alert/confirm/prompt Ersatz) ---------- */
.bericht-dolmodal-overlay {
position: fixed; inset: 0;
background: rgba(0,0,0,0.55);
z-index: 100000;
display: flex; align-items: center; justify-content: center;
}
.bericht-dolmodal {
background: var(--colorbackbody, #22222a);
color: var(--colortext, #eee);
border: 1px solid var(--colorboxbordertitle1, #444);
border-radius: 6px;
min-width: 320px; max-width: 520px;
box-shadow: 0 10px 40px rgba(0,0,0,0.6);
font-size: 14px;
}
.bericht-dolmodal-title {
padding: 10px 14px;
font-weight: bold;
background: var(--colorbacktitle1, #2d2d38);
border-bottom: 1px solid var(--colorboxbordertitle1, #444);
border-radius: 6px 6px 0 0;
}
.bericht-dolmodal-body {
padding: 16px 14px;
line-height: 1.5;
white-space: pre-wrap;
}
.bericht-dolmodal-input {
display: block;
width: 100%;
margin-top: 10px;
padding: 6px 8px;
background: var(--colorbackbody, #1a1a1f);
color: var(--colortext, #eee);
border: 1px solid var(--colorboxbordertitle1, #555);
border-radius: 3px;
box-sizing: border-box;
}
.bericht-dolmodal-foot {
padding: 10px 14px;
display: flex; gap: 8px; justify-content: flex-end;
border-top: 1px solid var(--colorboxbordertitle1, #444);
}

View file

@ -865,7 +865,7 @@
const tplBtn = document.getElementById('btn-save-as-template'); const tplBtn = document.getElementById('btn-save-as-template');
if (tplBtn) { if (tplBtn) {
tplBtn.addEventListener('click', async () => { tplBtn.addEventListener('click', async () => {
const label = prompt('Label für die Vorlage:\n(z. B. "PV-Anlage Standard" oder "Wallbox 11kW")'); const label = await dolPrompt('Label für die Vorlage (z. B. "PV-Anlage Standard" oder "Wallbox 11kW")');
if (!label) return; if (!label) return;
await savePageAnnotations(false); await savePageAnnotations(false);
const fd = new FormData(); const fd = new FormData();
@ -875,12 +875,12 @@
const r = await fetch(cfg.urls.save_as_template, { method: 'POST', body: fd }); const r = await fetch(cfg.urls.save_as_template, { method: 'POST', body: fd });
const data = await r.json(); const data = await r.json();
if (data.success) toast('✓ Vorlage "' + label + '" gespeichert'); if (data.success) toast('✓ Vorlage "' + label + '" gespeichert');
else alert('Fehler: ' + (data.error || '')); else dolAlert('Fehler: ' + (data.error || ''));
}); });
} }
document.getElementById('btn-finalize').addEventListener('click', async () => { document.getElementById('btn-finalize').addEventListener('click', async () => {
if (!confirm('Bericht jetzt finalisieren und PDF erzeugen?')) return; if (!(await dolConfirm('Bericht jetzt finalisieren und PDF erzeugen?'))) return;
toast('Speichere aktuelle Seite…'); toast('Speichere aktuelle Seite…');
await savePageAnnotations(false); await savePageAnnotations(false);
toast('PDF wird erzeugt…'); toast('PDF wird erzeugt…');
@ -918,7 +918,7 @@
btn.addEventListener('click', async () => { btn.addEventListener('click', async () => {
const checks = document.querySelectorAll('.att-check:checked'); const checks = document.querySelectorAll('.att-check:checked');
if (!checks.length) { if (!checks.length) {
alert('Bitte zuerst Bilder ankreuzen'); dolAlert('Bitte zuerst Bilder ankreuzen');
return; return;
} }
const layout = layoutSel ? layoutSel.value : 'single'; const layout = layoutSel ? layoutSel.value : 'single';
@ -942,7 +942,7 @@
const slotCount = { grid_2: 2, grid_2v: 2, grid_4: 4, grid_6: 6, before_after: 2 }[layout] || 4; const slotCount = { grid_2: 2, grid_2v: 2, grid_4: 4, grid_6: 6, before_after: 2 }[layout] || 4;
const imageChecks = Array.from(checks).filter(c => c.dataset.mime.startsWith('image')); const imageChecks = Array.from(checks).filter(c => c.dataset.mime.startsWith('image'));
if (!imageChecks.length) { if (!imageChecks.length) {
alert('Bitte mindestens ein Bild ankreuzen (PDFs nicht in Grids unterstützt)'); dolAlert('Bitte mindestens ein Bild ankreuzen (PDFs nicht in Grids unterstützt)');
return; return;
} }
// In Gruppen à slotCount aufteilen // In Gruppen à slotCount aufteilen
@ -956,7 +956,7 @@
const r = await fetch(cfg.urls.create_grid_page, { method: 'POST', body: fd }); const r = await fetch(cfg.urls.create_grid_page, { method: 'POST', body: fd });
const data = await r.json().catch(() => ({})); const data = await r.json().catch(() => ({}));
if (!data.success) { if (!data.success) {
alert('Fehler bei Gruppe '+(Math.floor(i/slotCount)+1)+': '+(data.error || 'unbekannt')); dolAlert('Fehler bei Gruppe '+(Math.floor(i/slotCount)+1)+': '+(data.error || 'unbekannt'));
return; return;
} }
} }
@ -972,7 +972,7 @@
const rel = btn.dataset.relpath; const rel = btn.dataset.relpath;
const ref = btn.dataset.sourceRef || ''; const ref = btn.dataset.sourceRef || '';
const name = btn.parentElement.querySelector('.att-name')?.textContent || rel; const name = btn.parentElement.querySelector('.att-name')?.textContent || rel;
if (!confirm('Datei "' + name + '" aus ' + (ref ? ref : 'dem Anhang') + ' wirklich löschen?\n\nDiese Aktion kann nicht rückgängig gemacht werden.')) return; if (!(await dolConfirm('Datei "' + name + '" aus ' + (ref ? ref : 'dem Anhang') + ' wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.'))) return;
const fd = new FormData(); const fd = new FormData();
fd.append('token', cfg.token); fd.append('token', cfg.token);
fd.append('relpath', rel); fd.append('relpath', rel);
@ -982,7 +982,7 @@
btn.parentElement.remove(); btn.parentElement.remove();
toast('Datei gelöscht'); toast('Datei gelöscht');
} else { } else {
alert('Löschen fehlgeschlagen: ' + (data.error || 'unbekannt')); dolAlert('Löschen fehlgeschlagen: ' + (data.error || 'unbekannt'));
} }
}); });
}); });
@ -1000,7 +1000,7 @@
const r = await fetch(cfg.urls.upload_extra, { method: 'POST', body: fd }); const r = await fetch(cfg.urls.upload_extra, { method: 'POST', body: fd });
const data = await r.json(); const data = await r.json();
if (data.success) location.reload(); if (data.success) location.reload();
else alert('Upload fehlgeschlagen: ' + (data.error || '')); else dolAlert('Upload fehlgeschlagen: ' + (data.error || ''));
}); });
} }
@ -1023,7 +1023,7 @@
const r = await fetch(cfg.urls.create_upload_token, { method: 'POST', body: fd }); const r = await fetch(cfg.urls.create_upload_token, { method: 'POST', body: fd });
const data = await r.json(); const data = await r.json();
if (!data.success) { if (!data.success) {
alert('Token-Erstellung fehlgeschlagen: ' + (data.error || '')); dolAlert('Token-Erstellung fehlgeschlagen: ' + (data.error || ''));
return; return;
} }
const url = data.url; const url = data.url;
@ -1080,7 +1080,7 @@
const del = t.querySelector('.thumb-del'); const del = t.querySelector('.thumb-del');
if (del) del.addEventListener('click', async (e) => { if (del) del.addEventListener('click', async (e) => {
e.stopPropagation(); e.stopPropagation();
if (!confirm(cfg.lang.confirm_del)) return; if (!(await dolConfirm(cfg.lang.confirm_del))) return;
const fd = new FormData(); const fd = new FormData();
fd.append('token', cfg.token); fd.append('token', cfg.token);
fd.append('pageid', t.dataset.pageid); fd.append('pageid', t.dataset.pageid);
@ -1111,13 +1111,13 @@
const data = await r.json(); const data = await r.json();
btn.textContent = '🔍'; btn.textContent = '🔍';
if (!data.success) { if (!data.success) {
alert('Fehler: ' + (data.error || 'unbekannt')); dolAlert('Fehler: ' + (data.error || 'unbekannt'));
return; return;
} }
showSignatureVerifyResult(data); showSignatureVerifyResult(data);
} catch (err) { } catch (err) {
btn.textContent = '🔍'; btn.textContent = '🔍';
alert('Netzwerkfehler: ' + err.message); dolAlert('Netzwerkfehler: ' + err.message);
} }
}); });
}); });
@ -1261,5 +1261,97 @@
setTimeout(() => t.remove(), 2000); setTimeout(() => t.remove(), 2000);
} }
/* ---------- Dolibarr-Style Modal-Dialoge (ersetzen alert/confirm/prompt) ---------- */
function dolModal(opts) {
// opts: { title, body, buttons:[{label, value, primary}], input:boolean, defaultValue }
return new Promise((resolve) => {
const ov = document.createElement('div');
ov.className = 'bericht-dolmodal-overlay';
const box = document.createElement('div');
box.className = 'bericht-dolmodal';
const h = document.createElement('div');
h.className = 'bericht-dolmodal-title';
h.textContent = opts.title || 'Bericht';
const b = document.createElement('div');
b.className = 'bericht-dolmodal-body';
b.textContent = opts.body || '';
let inputEl = null;
if (opts.input) {
inputEl = document.createElement('input');
inputEl.type = 'text';
inputEl.className = 'bericht-dolmodal-input';
inputEl.value = opts.defaultValue || '';
b.appendChild(document.createElement('br'));
b.appendChild(inputEl);
}
const foot = document.createElement('div');
foot.className = 'bericht-dolmodal-foot';
(opts.buttons || [{label:'OK', value:true, primary:true}]).forEach(btn => {
const el = document.createElement('button');
el.type = 'button';
el.className = btn.primary ? 'butAction butActionConfirm' : 'butAction';
el.textContent = btn.label;
el.addEventListener('click', () => {
const val = opts.input ? (btn.value ? (inputEl.value || null) : null) : btn.value;
ov.remove();
resolve(val);
});
foot.appendChild(el);
});
box.appendChild(h); box.appendChild(b); box.appendChild(foot);
ov.appendChild(box);
document.body.appendChild(ov);
if (inputEl) { inputEl.focus(); inputEl.select(); }
ov.addEventListener('click', (e) => {
if (e.target === ov) { ov.remove(); resolve(opts.input ? null : false); }
});
});
}
async function dolAlert(msg, title) {
return dolModal({
title: title || 'Hinweis',
body: msg,
buttons: [{label:'OK', value:true, primary:true}],
});
}
async function dolConfirm(msg, title) {
return dolModal({
title: title || 'Bestätigen',
body: msg,
buttons: [
{label:'Abbrechen', value:false},
{label:'OK', value:true, primary:true},
],
});
}
async function dolPrompt(msg, defaultValue, title) {
return dolModal({
title: title || 'Eingabe',
body: msg,
input: true,
defaultValue: defaultValue || '',
buttons: [
{label:'Abbrechen', value:false},
{label:'OK', value:true, primary:true},
],
});
}
// Globaler Handler: Links/Buttons mit data-dolconfirm abfangen
document.addEventListener('click', async (e) => {
const el = e.target.closest('[data-dolconfirm]');
if (!el) return;
if (el.dataset._dolconfirmed === '1') return; // schon durchgelaufen
e.preventDefault();
e.stopPropagation();
const ok = await dolConfirm(el.getAttribute('data-dolconfirm'));
if (!ok) return;
el.dataset._dolconfirmed = '1';
if (el.tagName === 'A') {
window.location.href = el.getAttribute('href');
} else {
el.click();
}
}, true);
document.addEventListener('DOMContentLoaded', init); document.addEventListener('DOMContentLoaded', init);
})(); })();