Fix duplicate trash drop listener and rectangle selection bugs

- Move trash button drop listener out of setupDrag() to an IIFE so it
  only registers once (was accumulating listeners on every render() call,
  causing multiple toast messages on single drop).
- Fix rectangle selection mousedown handler: use optional chaining on
  closest() for safety with text nodes; avoid calling render() inside
  mousedown (which would detach the event target's children mid-handler)
  and instead just remove .selected class directly from the DOM.
This commit is contained in:
2026-05-09 14:34:20 +01:00
parent 5da0deaf94
commit a97d05412e
+24 -17
View File
@@ -869,21 +869,6 @@ function setupDrag() {
el.ondragleave = function(e) { this.classList.remove('drag-over'); }; el.ondragleave = function(e) { this.classList.remove('drag-over'); };
el.ondrop = function(e) { e.preventDefault(); e.stopPropagation(); this.classList.remove('drag-over'); const fromId = parseInt(e.dataTransfer.getData('text/plain')); const toId = parseInt(this.dataset.id); if (!fromId || !toId) return; const ids = selectedIds.has(fromId) && selectedIds.size > 1 ? [...selectedIds] : [fromId]; if (ids.length === 1 && ids[0] === toId) return; const base = order.length > 0 ? order : entries.filter(e => e).map(e => e.id); const filtered = base.filter(id => !ids.includes(id)); const idx = filtered.indexOf(toId); idx > -1 ? filtered.splice(idx, 0, ...ids) : filtered.push(...ids); order = filtered; localStorage.setItem('entryOrder', JSON.stringify(order)); const map = new Map(entries.filter(e => e).map(e => [e.id, e])); entries = order.map(id => map.get(id)).filter(e => e); entries.sort((a, b) => (b.favorite || 0) - (a.favorite || 0)); render(); }; el.ondrop = function(e) { e.preventDefault(); e.stopPropagation(); this.classList.remove('drag-over'); const fromId = parseInt(e.dataTransfer.getData('text/plain')); const toId = parseInt(this.dataset.id); if (!fromId || !toId) return; const ids = selectedIds.has(fromId) && selectedIds.size > 1 ? [...selectedIds] : [fromId]; if (ids.length === 1 && ids[0] === toId) return; const base = order.length > 0 ? order : entries.filter(e => e).map(e => e.id); const filtered = base.filter(id => !ids.includes(id)); const idx = filtered.indexOf(toId); idx > -1 ? filtered.splice(idx, 0, ...ids) : filtered.push(...ids); order = filtered; localStorage.setItem('entryOrder', JSON.stringify(order)); const map = new Map(entries.filter(e => e).map(e => [e.id, e])); entries = order.map(id => map.get(id)).filter(e => e); entries.sort((a, b) => (b.favorite || 0) - (a.favorite || 0)); render(); };
}); });
// Trash button as drop target for multi-delete
const trashBtn = document.getElementById('trashBtn');
if (trashBtn) {
trashBtn.addEventListener('dragover', e => { if (!showTrash) { e.preventDefault(); trashBtn.classList.add('drag-over'); } });
trashBtn.addEventListener('dragleave', () => trashBtn.classList.remove('drag-over'));
trashBtn.addEventListener('drop', async function(e) {
e.preventDefault();
this.classList.remove('drag-over');
const id = parseInt(e.dataTransfer.getData('text/plain'));
if (!id) return;
const ids = selectedIds.has(id) && selectedIds.size > 1 ? [...selectedIds] : [id];
for (const sid of ids) await delEntry(sid);
clearSelection();
});
}
} }
// ==================== EDIT ==================== // ==================== EDIT ====================
@@ -1138,16 +1123,38 @@ if (token && curUser) {
} }
['click', 'keypress', 'scroll', 'mousemove'].forEach(e => document.addEventListener(e, () => { if (token) resetIdle(); })); ['click', 'keypress', 'scroll', 'mousemove'].forEach(e => document.addEventListener(e, () => { if (token) resetIdle(); }));
// Trash button as drop target (set up once, outside setupDrag to avoid duplicates)
(function() {
const trashBtn = document.getElementById('trashBtn');
if (trashBtn) {
trashBtn.addEventListener('dragover', e => { if (!showTrash) { e.preventDefault(); trashBtn.classList.add('drag-over'); } });
trashBtn.addEventListener('dragleave', () => trashBtn.classList.remove('drag-over'));
trashBtn.addEventListener('drop', async function(e) {
e.preventDefault();
this.classList.remove('drag-over');
const id = parseInt(e.dataTransfer.getData('text/plain'));
if (!id) return;
const ids = selectedIds.has(id) && selectedIds.size > 1 ? [...selectedIds] : [id];
for (const sid of ids) await delEntry(sid);
clearSelection();
});
}
})();
// Rectangle selection // Rectangle selection
document.getElementById('entriesContainer').addEventListener('mousedown', function(e) { document.getElementById('entriesContainer').addEventListener('mousedown', function(e) {
if (rectState.active || e.button !== 0) return; if (rectState.active || e.button !== 0) return;
if (e.target.closest('[draggable="true"]') || e.target.closest('.action-btns')) return; if (e.target?.closest?.('[draggable="true"]') || e.target?.closest?.('.action-btns')) return;
rectState.active = true; rectState.active = true;
rectState.startX = e.clientX; rectState.startX = e.clientX;
rectState.startY = e.clientY; rectState.startY = e.clientY;
rectState.started = false; rectState.started = false;
rectState.el = null; rectState.el = null;
clearSelection(); // Clear selection without full re-render to avoid detaching event target
selectedIds.clear();
lastSelectedId = null;
hideBatchBar();
document.getElementById('entriesContainer')?.querySelectorAll('.selected').forEach(el => el.classList.remove('selected'));
}); });
document.addEventListener('mousemove', function(e) { document.addEventListener('mousemove', function(e) {
if (!rectState.active) return; if (!rectState.active) return;