Fix batch confirm: modal overlay + positioned beside button

Replace showCenterConfirm with showBatchConfirm(btn,message,callback) that creates a transparent modal overlay (z-index 9999) to block background clicks, positions the confirm dialog beside the trigger button. Updated all callers (batchDelete, batchPermanentDelete, permanentDelete, emptyTrash) to pass the clicked button. Added CSS for batch-confirm-overlay backdrop.
This commit is contained in:
2026-05-09 18:37:57 +01:00
parent 131bb91cd2
commit 02e5868d66
2 changed files with 28 additions and 16 deletions
+24 -14
View File
@@ -305,10 +305,13 @@ async function permanentDelete(id, silent) {
try { const r = await fetch(API + '/entries/' + id + '?permanent=1', { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token, 'X-CSRF-Token': csrfToken } }); if (r.ok) { order = order.filter(x => x != id); localStorage.setItem('entryOrder', JSON.stringify(order)); if (!silent) { toast('🗑️ Permanently deleted'); await loadEntries(); playSound('error'); } } } catch (e) { if (!silent) toast('⚠️ Error', 'error'); }
};
if (silent) { await doDelete(); return; }
showCenterConfirm('Permanently delete?', doDelete);
const btn = document.querySelector('.delete-btn[data-id="' + id + '"]');
if (btn) showBatchConfirm(btn, 'Permanently delete?', doDelete);
else showBatchConfirm(document.body, 'Permanently delete?', doDelete);
}
async function emptyTrash() {
showCenterConfirm('Delete ALL trashed entries?', async () => {
const btn = document.querySelector('.empty-trash-btn');
showBatchConfirm(btn || document.body, 'Delete ALL trashed entries?', async () => {
try { const r = await fetch(API + '/entries/trash/empty', { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token, 'X-CSRF-Token': csrfToken } }); if (r.ok) { toast('🗑️ Trash emptied'); await loadEntries(); playSound('error'); } } catch (e) { toast('⚠️ Error', 'error'); }
});
}
@@ -941,35 +944,40 @@ function showConfirm(btn, message, callback) {
}, 10);
}
function entryPw(id) { const e = entries.find(x => x.id == id); return e ? e.password : ''; }
function showCenterConfirm(message, callback) {
const existing = document.querySelector('.custom-confirm');
function showBatchConfirm(btn, message, callback) {
const existing = document.querySelector('.batch-confirm-overlay');
if (existing) existing.remove();
const overlay = document.createElement('div');
overlay.className = 'batch-confirm-overlay';
overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;z-index:9999;background:transparent;';
const confirm = document.createElement('div');
confirm.className = 'custom-confirm show';
confirm.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:10000;';
confirm.style.cssText = 'position:fixed;background:var(--bg2);border:1px solid var(--accent);border-radius:0.8rem;padding:0.7rem 1rem;z-index:10000;box-shadow:0 10px 30px rgba(0,0,0,0.5);font-size:0.8rem;color:var(--text);white-space:nowrap;';
confirm.innerHTML =
'<div class="confirm-text">' + message + '</div>' +
'<div class="confirm-btns">' +
'<button class="confirm-yes">Yes</button>' +
'<button class="confirm-no">No</button>' +
'</div>';
document.body.appendChild(confirm);
const rect = btn.getBoundingClientRect();
confirm.style.top = (rect.top - 60) + 'px';
let leftPos = rect.left - 20;
if (leftPos < 10) leftPos = 10;
confirm.style.left = leftPos + 'px';
overlay.appendChild(confirm);
document.body.appendChild(overlay);
const yesBtn = confirm.querySelector('.confirm-yes');
const noBtn = confirm.querySelector('.confirm-no');
const cleanup = () => { confirm.remove(); document.removeEventListener('keydown', keyHandler); };
const cleanup = () => { overlay.remove(); document.removeEventListener('keydown', keyHandler); };
const keyHandler = (e) => {
if (e.key === 'Enter' || e.key === 'y' || e.key === 'Y') { e.preventDefault(); cleanup(); callback(); playSound('delete'); }
else if (e.key === 'Escape' || e.key === 'n' || e.key === 'N') { e.preventDefault(); cleanup(); }
};
yesBtn.onclick = () => { cleanup(); callback(); playSound('delete'); };
noBtn.onclick = () => cleanup();
overlay.onclick = (e) => { if (e.target === overlay) cleanup(); };
confirm.tabIndex = 0; confirm.focus();
document.addEventListener('keydown', keyHandler);
setTimeout(() => {
document.addEventListener('click', function closeConfirm(e) {
if (!confirm.contains(e.target)) { cleanup(); document.removeEventListener('click', closeConfirm); }
});
}, 10);
}
function attachEvents() {
document.querySelectorAll('.delete-btn').forEach(b => b.onclick = function(ev) { ev.stopPropagation(); if (showTrash) { permanentDelete(this.dataset.id); } else { showConfirm(this, 'Delete this entry?', id => delEntry(id)); } });
@@ -1101,7 +1109,8 @@ function hideBatchBar() {
async function batchDelete() {
const count = selectedIds.size;
showCenterConfirm('Move ' + count + ' entries to trash?', async () => {
const btn = document.querySelector('#batchBar .btn-danger');
showBatchConfirm(btn || document.body, 'Move ' + count + ' entries to trash?', async () => {
for (const id of selectedIds) await delEntry(id, true);
toast('📦 Moved ' + count + ' entries to trash');
playSound('delete');
@@ -1112,7 +1121,8 @@ async function batchDelete() {
async function batchPermanentDelete() {
const count = selectedIds.size;
showCenterConfirm('Permanently delete ' + count + ' entries?', async () => {
const btn = document.querySelector('#batchBar .btn-danger');
showBatchConfirm(btn || document.body, 'Permanently delete ' + count + ' entries?', async () => {
for (const id of selectedIds) await permanentDelete(id, true);
toast('🗑️ Permanently deleted ' + count + ' entries');
playSound('error');