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:
+3
-1
@@ -215,7 +215,7 @@ input:focus, select:focus { border-color:var(--accent); }
|
|||||||
/* Trash */
|
/* Trash */
|
||||||
.trash-badge { background:var(--danger); color:#fff; padding:0.15rem 0.5rem; border-radius:1rem; font-size:0.65rem; margin-left:0.3rem; }
|
.trash-badge { background:var(--danger); color:#fff; padding:0.15rem 0.5rem; border-radius:1rem; font-size:0.65rem; margin-left:0.3rem; }
|
||||||
.trash-info { font-size:0.7rem; color:var(--text2); margin-top:0.2rem; }
|
.trash-info { font-size:0.7rem; color:var(--text2); margin-top:0.2rem; }
|
||||||
.restore-btn { background:var(--success); color:#fff; border:none; padding:0.2rem 0.6rem; border-radius:1.5rem; cursor:pointer; font-size:0.7rem; }
|
.restore-btn { background:none; color:#fff; border:none; padding:0.2rem 0.6rem; border-radius:1.5rem; cursor:pointer; font-size:0.7rem; }
|
||||||
.restore-btn:hover { filter:brightness(1.2); }
|
.restore-btn:hover { filter:brightness(1.2); }
|
||||||
.empty-trash-btn { background:var(--danger); color:#fff; border:none; padding:0.3rem 0.8rem; border-radius:1.5rem; cursor:pointer; font-size:0.75rem; }
|
.empty-trash-btn { background:var(--danger); color:#fff; border:none; padding:0.3rem 0.8rem; border-radius:1.5rem; cursor:pointer; font-size:0.75rem; }
|
||||||
.empty-trash-btn:hover { filter:brightness(1.2); }
|
.empty-trash-btn:hover { filter:brightness(1.2); }
|
||||||
@@ -332,3 +332,5 @@ input:focus, select:focus { border-color:var(--accent); }
|
|||||||
box-shadow:0 0 15px rgba(239,68,68,0.4);
|
box-shadow:0 0 15px rgba(239,68,68,0.4);
|
||||||
background:rgba(239,68,68,0.15);
|
background:rgba(239,68,68,0.15);
|
||||||
}
|
}
|
||||||
|
/* Batch confirm modal backdrop */
|
||||||
|
.batch-confirm-overlay { background:rgba(0,0,0,0.3); }
|
||||||
@@ -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'); }
|
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; }
|
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() {
|
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'); }
|
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);
|
}, 10);
|
||||||
}
|
}
|
||||||
function entryPw(id) { const e = entries.find(x => x.id == id); return e ? e.password : ''; }
|
function entryPw(id) { const e = entries.find(x => x.id == id); return e ? e.password : ''; }
|
||||||
function showCenterConfirm(message, callback) {
|
function showBatchConfirm(btn, message, callback) {
|
||||||
const existing = document.querySelector('.custom-confirm');
|
const existing = document.querySelector('.batch-confirm-overlay');
|
||||||
if (existing) existing.remove();
|
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');
|
const confirm = document.createElement('div');
|
||||||
confirm.className = 'custom-confirm show';
|
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 =
|
confirm.innerHTML =
|
||||||
'<div class="confirm-text">' + message + '</div>' +
|
'<div class="confirm-text">' + message + '</div>' +
|
||||||
'<div class="confirm-btns">' +
|
'<div class="confirm-btns">' +
|
||||||
'<button class="confirm-yes">Yes</button>' +
|
'<button class="confirm-yes">Yes</button>' +
|
||||||
'<button class="confirm-no">No</button>' +
|
'<button class="confirm-no">No</button>' +
|
||||||
'</div>';
|
'</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 yesBtn = confirm.querySelector('.confirm-yes');
|
||||||
const noBtn = confirm.querySelector('.confirm-no');
|
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) => {
|
const keyHandler = (e) => {
|
||||||
if (e.key === 'Enter' || e.key === 'y' || e.key === 'Y') { e.preventDefault(); cleanup(); callback(); playSound('delete'); }
|
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(); }
|
else if (e.key === 'Escape' || e.key === 'n' || e.key === 'N') { e.preventDefault(); cleanup(); }
|
||||||
};
|
};
|
||||||
yesBtn.onclick = () => { cleanup(); callback(); playSound('delete'); };
|
yesBtn.onclick = () => { cleanup(); callback(); playSound('delete'); };
|
||||||
noBtn.onclick = () => cleanup();
|
noBtn.onclick = () => cleanup();
|
||||||
|
overlay.onclick = (e) => { if (e.target === overlay) cleanup(); };
|
||||||
confirm.tabIndex = 0; confirm.focus();
|
confirm.tabIndex = 0; confirm.focus();
|
||||||
document.addEventListener('keydown', keyHandler);
|
document.addEventListener('keydown', keyHandler);
|
||||||
setTimeout(() => {
|
|
||||||
document.addEventListener('click', function closeConfirm(e) {
|
|
||||||
if (!confirm.contains(e.target)) { cleanup(); document.removeEventListener('click', closeConfirm); }
|
|
||||||
});
|
|
||||||
}, 10);
|
|
||||||
}
|
}
|
||||||
function attachEvents() {
|
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)); } });
|
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() {
|
async function batchDelete() {
|
||||||
const count = selectedIds.size;
|
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);
|
for (const id of selectedIds) await delEntry(id, true);
|
||||||
toast('📦 Moved ' + count + ' entries to trash');
|
toast('📦 Moved ' + count + ' entries to trash');
|
||||||
playSound('delete');
|
playSound('delete');
|
||||||
@@ -1112,7 +1121,8 @@ async function batchDelete() {
|
|||||||
|
|
||||||
async function batchPermanentDelete() {
|
async function batchPermanentDelete() {
|
||||||
const count = selectedIds.size;
|
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);
|
for (const id of selectedIds) await permanentDelete(id, true);
|
||||||
toast('🗑️ Permanently deleted ' + count + ' entries');
|
toast('🗑️ Permanently deleted ' + count + ' entries');
|
||||||
playSound('error');
|
playSound('error');
|
||||||
|
|||||||
Reference in New Issue
Block a user